入门课

TIP

内容来自之前为内部同学准备的入门课。

React 没有解决的问题

React 本身只是一个 DOM 的抽象层,使用组件构建虚拟 DOM。

如果开发大应用,还需要解决一个问题。

  • 通信:组件之间如何通信?
  • 数据流:数据如何和视图串联起来?路由和数据如何绑定?如何编写异步逻辑?等等

通信问题

组件会发生三种通信。

  • 向子组件发消息
  • 向父组件发消息
  • 向其他组件发消息React 只提供了一种通信手段:传参。对于大应用,很不方便。

组件通信的例子

步骤1

  1. class Son extends React.Component {
  2. render() {
  3. return <input/>;
  4. }
  5. }
  6. class Father extends React.Component {
  7. render() {
  8. return <div>
  9. <Son/>
  10. <p>这里显示 Son 组件的内容</p>
  11. </div>;
  12. }
  13. }
  14. ReactDOM.render(<Father/>, mountNode);

看这个例子,想一想父组件如何拿到子组件的值。

步骤2

  1. class Son extends React.Component {
  2. render() {
  3. return <input onChange={this.props.onChange}/>;
  4. }
  5. }
  6. class Father extends React.Component {
  7. constructor() {
  8. super();
  9. this.state = {
  10. son: ""
  11. }
  12. }
  13. changeHandler(e) {
  14. this.setState({
  15. son: e.target.value
  16. });
  17. }
  18. render() {
  19. return <div>
  20. <Son onChange={this.changeHandler.bind(this)}/>
  21. <p>这里显示 Son 组件的内容:{this.state.son}</p>
  22. </div>;
  23. }
  24. }
  25. ReactDOM.render(<Father/>, mountNode);

看下这个例子,看懂源码,理解子组件如何通过父组件传入的函数,将自己的值再传回父组件。

数据流问题

目前流行的数据流方案有:

  • Flux,单向数据流方案,以 Redux 为代表
  • Reactive,响应式数据流方案,以 Mobx 为代表
  • 其他,比如 rxjs 等到底哪一种架构最合适 React ?

目前最流行的数据流方案

截止 2017.1,最流行的社区 React 应用架构方案如下。

dva 是什么

dva 是体验技术部开发的 React 应用框架,将上面三个 React 工具库包装在一起,简化了 API,让开发 React 应用更加方便和快捷。

dva = React-Router + Redux + Redux-saga

dva 应用的最简结构

  1. import dva from 'dva';
  2. const App = () => <div>Hello dva</div>;
  3. // 创建应用
  4. const app = dva();
  5. // 注册视图
  6. app.router(() => <App />);
  7. // 启动应用
  8. app.start('#root');

数据流图

入门课 - 图1

核心概念

  • State:一个对象,保存整个应用状态
  • View:React 组件构成的视图层
  • Action:一个对象,描述事件
  • connect 方法:一个函数,绑定 State 到 View
  • dispatch 方法:一个函数,发送 Action 到 State

State 和 View

State 是储存数据的地方,收到 Action 以后,会更新数据。

View 就是 React 组件构成的 UI 层,从 State 取数据后,渲染成 HTML 代码。只要 State 有变化,View 就会自动更新。

Action

Action 是用来描述 UI 层事件的一个对象。

  1. {
  2. type: 'click-submit-button',
  3. payload: this.form.data
  4. }

connect 方法

connect 是一个函数,绑定 State 到 View。

  1. import { connect } from 'dva';
  2. function mapStateToProps(state) {
  3. return { todos: state.todos };
  4. }
  5. connect(mapStateToProps)(App);

connect 方法返回的也是一个 React 组件,通常称为容器组件。因为它是原始 UI 组件的容器,即在外面包了一层 State。

connect 方法传入的第一个参数是 mapStateToProps 函数,mapStateToProps 函数会返回一个对象,用于建立 State 到 Props 的映射关系。

dispatch 方法

dispatch 是一个函数方法,用来将 Action 发送给 State。

  1. dispatch({
  2. type: 'click-submit-button',
  3. payload: this.form.data
  4. })

dispatch 方法从哪里来?被 connect 的 Component 会自动在 props 中拥有 dispatch 方法。

connect 的数据从哪里来?

dva 应用的最简结构(带 model)

  1. // 创建应用
  2. const app = dva();
  3. // 注册 Model
  4. app.model({
  5. namespace: 'count',
  6. state: 0,
  7. reducers: {
  8. add(state) { return state + 1 },
  9. },
  10. effects: {
  11. *addAfter1Second(action, { call, put }) {
  12. yield call(delay, 1000);
  13. yield put({ type: 'add' });
  14. },
  15. },
  16. });
  17. // 注册视图
  18. app.router(() => <ConnectedApp />);
  19. // 启动应用
  20. app.start('#root');

数据流图 1

入门课 - 图2

数据流图 2

入门课 - 图3

app.model

dva 提供 app.model 这个对象,所有的应用逻辑都定义在它上面。

  1. const app = dva();
  2. // 新增这一行
  3. app.model({ /**/ });
  4. app.router(() => <App />);
  5. app.start('#root');

Model 对象的例子

  1. {
  2. namespace: 'count',
  3. state: 0,
  4. reducers: {
  5. add(state) { return state + 1 },
  6. },
  7. effects: {
  8. *addAfter1Second(action, { call, put }) {
  9. yield call(delay, 1000);
  10. yield put({ type: 'add' });
  11. },
  12. },
  13. }

Model 对象的属性

  • namespace: 当前 Model 的名称。整个应用的 State,由多个小的 Model 的 State 以 namespace 为 key 合成
  • state: 该 Model 当前的状态。数据保存在这里,直接决定了视图层的输出
  • reducers: Action 处理器,处理同步动作,用来算出最新的 State
  • effects:Action 处理器,处理异步动作

Reducer

Reducer 是 Action 处理器,用来处理同步操作,可以看做是 state 的计算器。它的作用是根据 Action,从上一个 State 算出当前 State。

一些例子:

  1. // count +1
  2. function add(state) { return state + 1; }
  3. // 往 [] 里添加一个新 todo
  4. function addTodo(state, action) { return [...state, action.payload]; }
  5. // 往 { todos: [], loading: true } 里添加一个新 todo,并标记 loading 为 false
  6. function addTodo(state, action) {
  7. return {
  8. ...state,
  9. todos: state.todos.concat(action.payload),
  10. loading: false
  11. };
  12. }

Effect

Action 处理器,处理异步动作,基于 Redux-saga 实现。Effect 指的是副作用。根据函数式编程,计算以外的操作都属于 Effect,典型的就是 I/O 操作、数据库读写。

  1. function *addAfter1Second(action, { put, call }) {
  2. yield call(delay, 1000);
  3. yield put({ type: 'add' });
  4. }

Generator 函数

Effect 是一个 Generator 函数,内部使用 yield 关键字,标识每一步的操作(不管是异步或同步)。

call 和 put

dva 提供多个 effect 函数内部的处理函数,比较常用的是 callput

  • call:执行异步函数
  • put:发出一个 Action,类似于 dispatch

课堂实战

写一个列表,包含删除按钮,点删除按钮后延迟 1 秒执行删除。入门课 - 图4