我的笔记——Redux

目录:

1、redux 简介

1.1 安装

  npm install --save redux

1.2 核心概念

  • action
  • reducer
  • store

1.3 三大原则

  • 单一数据源
  • State 是只读的
  • 使用纯函数来执行修改

1.4 设计思想

  • Web 应用是一个状态机,视图与状态是一一对应的。
  • 所有的状态,保存在一个对象里面。

2、你可能不需要 Redux

首先明确一点,Redux 是一个有用的架构,但不是非用不可。事实上,大多数情况,你可以不用它,只用 React 就够了。

如果你的 UI 层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性。如下

  • 用户的使用方式非常简单
  • 用户之间没有协作
  • 不需要与服务器大量交互,也没有使用 WebSocket
  • 视图层(View)只从单一来源获取数据

下面这些情况才是 Redux 的适用场景:多交互、多数据源。

  • 用户的使用方式复杂
  • 不同身份的用户有不同的使用方式(比如普通用户和管理员)
  • 多个用户之间可以协作
  • 与服务器大量交互,或者使用了 WebSocket
  • View 要从多个来源获取数据

从组件角度看,如果你的应用有以下场景,可以考虑使用 Redux。

  • 某个组件的状态,需要共享
  • 某个状态需要在任何地方都可以拿到
  • 一个组件需要改变全局状态
  • 一个组件需要改变另一个组件的状态

发生上面情况时,如果不使用 Redux 或者其他状态管理工具,不按照一定规律处理状态的读写,代码很快就会变成一团乱麻。你需要一种机制,可以在同一个地方查询状态改变状态传播状态的变化。

总之,不要把 Redux 当作万灵丹,如果你的应用没那么复杂,就没必要用它。另一方面,Redux 只是 Web 架构的一种解决方案,也可以选择其他方案。

3、Redux 涉及的基本概念

3.1 Store

Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。

Redux 提供 createStore这个函数,用来生成 Store。

import { createStore } from "redux";
const store = createStore(fn);

上面代码中,createStore 函数接受另一个函数作为参数,返回新生成的 Store 对象。

Store 就是把它们联系到一起的对象。Store 有以下职责(下面补充讲解)

- 维持应用的 state
- 提供 getState() 方法获取 state;
- 提供 dispatch(action) 方法更新 state;
- 通过 subscribe(listener) 注册监听器;
- 通过 subscribe(listener) 返回的函数注销监听器。

3.2 State

Store 对象包含所有数据。如果想得到某个时点的数据,就要对 Store 生成快照。这种时点的数据集合,就叫做 State。

当前时刻的 State,可以通过 store.getState( )拿到。

import { createStore } from "redux";
const store = createStore(fn);

const state = store.getState();

Redux 规定, 一个 State 对应一个 View。只要 State 相同,View 就相同。你知道 State,就知道 View 是什么样,反之亦然。

3.3 Action

State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。

Action 是一个对象。其中的type 属性是必须的,表示 Action 的名称。其他属性可以自由设置,社区有一个规范可以参考。

const action = {
  type: "ADD_TODO",
  payload: "Learn Redux",
};

上面代码中,Action 的名称是 ADD_TODO,它携带的信息是字符串 Learn Redux。

可以这样理解,Action 描述当前发生的事情。改变 State 的唯一办法,就是使用 Action。它会运送数据到 Store。

3.4 Action Creator

View 要发送多少种消息,就会有多少种 Action。如果都手写,会很麻烦。可以定义一个函数来生成 Action,这个函数就叫 Action Creator

const ADD_TODO = "添加 TODO";

function addTodo(text) {
  return {
    type: ADD_TODO,
    text,
  };
}

const action = addTodo("Learn Redux");

上面代码中,addTodo 函数就是一个 Action Creator。

3.5 store.dispatch( )

store.dispatch()是 View 发出 Action 的唯一方法。

import { createStore } from "redux";
const store = createStore(fn);

store.dispatch({
  type: "ADD_TODO",
  payload: "Learn Redux",
});

上面代码中,store.dispatch 接受一个 Action 对象作为参数,将它发送出去。

结合 Action Creator,这段代码可以改写如下。

store.dispatch(addTodo("Learn Redux"));

3.6 Reducer

Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer

Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。

const reducer = function(state, action) {
  // ...
  return new_state;
};

整个应用的初始状态,可以作为 State 的默认值。下面是一个实际的例子。

const defaultState = 0;
const reducer = (state = defaultState, action) => {
  switch (action.type) {
    case "ADD":
      return state + action.payload;
    default:
      return state;
  }
};

const state = reducer(1, {
  type: "ADD",
  payload: 2,
});

上面代码中,reducer 函数收到名为 ADD 的 Action 以后,就返回一个新的 State,作为加法的计算结果。其他运算的逻辑(比如减法),也可以根据 Action 的不同来实现。

实际应用中,Reducer 函数不用像上面这样手动调用,store.dispatch 方法会触发 Reducer 的自动执行。为此,Store 需要知道 Reducer 函数,做法就是在生成 Store 的时候,将 Reducer 传入 createStore 方法。

import { createStore } from "redux";
const store = createStore(reducer);

上面代码中,createStore 接受 Reducer 作为参数,生成一个新的 Store。以后每当 store.dispatch 发送过来一个新的 Action,就会自动调用 Reducer,得到新的 State。

为什么这个函数叫做 Reducer 呢?因为它可以作为数组的 reduce 方法(注:reduce 方法接收一个函数作为累加器)的参数。请看下面的例子,一系列 Action 对象按照顺序作为一个数组。

const actions = [
  { type: "ADD", payload: 0 },
  { type: "ADD", payload: 1 },
  { type: "ADD", payload: 2 },
];

const total = actions.reduce(reducer, 0); // 3

上面代码中,数组 actions 表示依次有三个 Action,分别是加 0、加 1 和加 2。数组的 reduce 方法接受 Reducer 函数作为参数,就可以直接得到最终的状态 3。

3.7 Reducer 的拆分

Reducer 函数负责生成 State。由于整个应用只有一个 State 对象,包含所有数据,对于大型应用来说,这个 State 必然十分庞大,导致 Reducer 函数也十分庞大。

const chatReducer = (state = defaultState, action = {}) => {
  const { type, payload } = action;
  switch (type) {
    case ADD_CHAT:
      return Object.assign({}, state, {
        chatLog: state.chatLog.concat(payload),
      });
    case CHANGE_STATUS:
      return Object.assign({}, state, {
        statusMessage: payload,
      });
    case CHANGE_USERNAME:
      return Object.assign({}, state, {
        userName: payload,
      });
    default:
      return state;
  }
};

这三个属性之间没有联系,这提示我们可以把 Reducer 函数拆分。不同的函数负责处理不同属性,最终把它们合并成一个大的 Reducer 即可。

const chatReducer = (state = defaultState, action = {}) => {
  return {
    chatLog: chatLog(state.chatLog, action),
    statusMessage: statusMessage(state.statusMessage, action),
    userName: userName(state.userName, action),
  };
};

上面代码中,Reducer 函数被拆成了三个小函数,每一个负责生成对应的属性。

Redux 提供了一个combineReducers方法,用于 Reducer 的拆分。你只要定义各个子 Reducer 函数,然后用这个方法,将它们合成一个大的 Reducer。

import { combineReducers } from "redux";

const chatReducer = combineReducers({
  chatLog,
  statusMessage,
  userName,
});

export default todoApp;

你可以把所有子 Reducer 放在一个文件里面,然后统一引入。

import { combineReducers } from "redux";
import * as reducers from "./reducers";

const reducer = combineReducers(reducers);

3.8 store.subscribe()

Store 允许使用 store.subscribe 方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。

import { createStore } from "redux";
const store = createStore(reducer);

store.subscribe(listener);

显然,只要把 View 的更新函数(对于 React 项目,就是组件的 render 方法或 setState 方法)放入 listen,就会实现 View 的自动渲染。

store.subscribe 方法返回一个函数,调用这个函数就可以解除监听

let unsubscribe = store.subscribe(() => console.log(store.getState()));

unsubscribe();

4、工作流程

本节对 Redux 的工作流程,做一个梳理。
我的笔记——Redux

// 首先,用户发出 Action。
store.dispatch(action);

// 然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action。 Reducer 会返回新的 State 。
let nextState = todoApp(previousState, action);

// State 一旦有变化,Store 就会调用监听函数。

// 设置监听函数
store.subscribe(listener);

// listener 可以通过 store.getState()得到当前状态。如果使用的是 React,这时可以触发重新渲染 View。
function listerner() {
  let newState = store.getState();
  component.setState(newState);
}

5、简单案例

[完整案例](https://github.com/reduxjs/redux/tree/master/examples

const Counter = ({ value, onIncrement, onDecrement }) => (
  <div>
    <h1>{value}</h1>
    <button onClick={onIncrement}>+</button>
    <button onClick={onDecrement}>-</button>
  </div>
);

const reducer = (state = 0, action) => {
  switch (action.type) {
    case "INCREMENT":
      return state + 1;
    case "DECREMENT":
      return state - 1;
    default:
      return state;
  }
};

const store = createStore(reducer);

const render = () => {
  ReactDOM.render(
    <Counter
      value={store.getState()}
      onIncrement={() => store.dispatch({ type: "INCREMENT" })}
      onDecrement={() => store.dispatch({ type: "DECREMENT" })}
    />,
    document.getElementById("root")
  );
};

render();
store.subscribe(render);

6、中间件的概念

上面介绍了 Redux 的基本做法:用户发出 Action,Reducer 函数算出新的 State,View 重新渲染

Action 发出以后,Reducer 立即算出 State,这叫做同步;Action 发出以后,过一段时间再执行 Reducer,这是异步。怎么才能 Reducer 在异步操作结束后自动执行呢?这就要用到新的工具:中间件(middleware)。

我的笔记——Redux

中间件就是一个函数,对 store.dispatch 方法进行了改造,在发出 Action 和执行 Reducer 这两步之间,添加了其他功能。

7、中间件的用法

例子:日志中间件,就有现成的 redux-logger 模块。

import { applyMiddleware, createStore } from "redux";
import createLogger from "redux-logger";
const logger = createLogger();

const store = createStore(reducer, applyMiddleware(logger));

上面代码中,redux-logger提供一个生成器 createLogger,可以生成日志中间件 logger。然后,将它放在 applyMiddleware 方法之中,传入 createStore 方法,就完成了 store.dispatch()的功能增强。

这里有两点需要注意:

//(1)、createStore 方法可以接受整个应用的初始状态作为参数,那样的话,applyMiddleware 就是第三个参数了。
const store = createStore(reducer, initial_state, applyMiddleware(logger));

//(2)、中间件的次序有讲究。(有的中间件有次序要求,使用前要查一下文档)
const store = createStore(reducer, applyMiddleware(thunk, promise, logger));

applyMiddlewares( )是 Redux 的原生方法,作用是将所有中间件组成一个数组,依次执行

8、异步操作的基本思路

理解了中间件以后,就可以处理异步操作了。同步操作只要发出一种 Action 即可,异步操作的差别是它要发出三种 Action。

  • 操作发起时的 Action
  • 操作成功时的 Action
  • 操作失败时的 Action

以向服务器取出数据为例,三种 Action 可以有两种不同的写法。

// 写法一:名称相同,参数不同
{ type: 'FETCH_POSTS' }
{ type: 'FETCH_POSTS', status: 'error', error: 'Oops' }
{ type: 'FETCH_POSTS', status: 'success', response: { ... } }

// 写法二:名称不同
{ type: 'FETCH_POSTS_REQUEST' }
{ type: 'FETCH_POSTS_FAILURE', error: 'Oops' }
{ type: 'FETCH_POSTS_SUCCESS', response: { ... } }

除了 Action 种类不同,异步操作的 State 也要进行改造,反映不同的操作状态。下面是 State 的一个例子。

let state = {
  // ...
  isFetching: true,
  didInvalidate: true,
  lastUpdated: "xxxxxxx",
};

上面代码中,State 的属性 isFetching 表示是否在抓取数据。didInvalidate 表示数据是否过时,lastUpdated 表示上一次更新时间。

现在,整个异步操作的思路就很清楚了。

  • 操作开始时,送出一个 Action,触发 State 更新为"正在操作"状态,View 重新渲染
  • 操作结束后,再送出一个 Action,触发 State 更新为"操作结束"状态,View 再一次重新渲染

9、redux-thunk 中间件

异步操作至少要送出两个 Action:用户触发第一个 Action,这个跟同步操作一样,没有问题;如何才能在操作结束时,系统自动送出第二个 Action 呢?

奥妙就在 Action Creator 之中。

class AsyncApp extends Component {
  componentDidMount() {
    const { dispatch, selectedPost } = this.props
    dispatch(fetchPosts(selectedPost))
  }

// ...

上面代码是一个异步组件的例子。加载成功后(componentDidMount 方法),它送出了(dispatch 方法)一个 Action,向服务器要求数据 fetchPosts(selectedSubreddit)。这里的 fetchPosts 就是 Action Creator。

下面就是 fetchPosts 的代码,关键之处就在里面。

const fetchPosts = postTitle => (dispatch, getState) => {
  dispatch(requestPosts(postTitle));
  return fetch(`/some/API/${postTitle}.json`)
    .then(response => response.json())
    .then(json => dispatch(receivePosts(postTitle, json)));
  };
};

// 使用方法一
store.dispatch(fetchPosts('reactjs'));
// 使用方法二
store.dispatch(fetchPosts('reactjs')).then(() =>
  console.log(store.getState())
);

上面代码中,fetchPosts 是一个 Action Creator(动作生成器),返回一个函数。这个函数执行后,先发出一个 Action(requestPosts(postTitle)),然后进行异步操作。拿到结果后,先将结果转成 JSON 格式,然后再发出一个 Action( receivePosts(postTitle, json))。

上面代码中,有几个地方需要注意。

  • fetchPosts 返回了一个函数,而普通的 Action Creator 默认返回一个对象。
  • 返回的函数的参数是 dispatch 和 getState 这两个 Redux 方法,普通的 Action Creator 的参数是 Action 的内容。
  • 在返回的函数之中,先发出一个 Action(requestPosts(postTitle)),表示操作开始。
  • 异步操作结束之后,再发出一个 Action(receivePosts(postTitle, json)),表示操作结束。

这样的处理,就解决了自动发送第二个 Action 的问题。但是,又带来了一个新的问题,Action 是由 store.dispatch 方法发送的。而 store.dispatch 方法正常情况下,参数只能是对象,不能是函数。

这时,就要使用中间件 redux-thunk。

import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import reducer from "./reducers";

// Note: this API requires redux@>=3.1.0
const store = createStore(reducer, applyMiddleware(thunk));

上面代码使用 redux-thunk 中间件,改造 store.dispatch,使得后者可以接受函数作为参数。

因此,异步操作的第一种解决方案就是,写出一个返回函数的 Action Creator,然后使用 redux-thunk 中间件改造 store.dispatch。

10、redux-promise 中间件

既然 Action Creator 可以返回函数,当然也可以返回其他值。另一种异步操作的解决方案,就是让 Action Creator 返回一个 Promise 对象。

这就需要使用redux-promise中间件。

import { createStore, applyMiddleware } from "redux";
import promiseMiddleware from "redux-promise";
import reducer from "./reducers";

const store = createStore(reducer, applyMiddleware(promiseMiddleware));

这个中间件使得 store.dispatch 方法可以接受 Promise 对象作为参数。这时,Action Creator 有两种写法。

// 写法一,返回值是一个 Promise 对象。
const fetchPosts =
  (dispatch, postTitle) => new Promise(function (resolve, reject) {
     dispatch(requestPosts(postTitle));
     return fetch(`/some/API/${postTitle}.json`)
       .then(response => {
         type: 'FETCH_POSTS',
         payload: response.json()
       });
});

// 写法二,Action 对象的payload属性是一个 Promise 对象。这需要从redux-actions模块引入createAction方法,并且写法也要变成下面这样。
import { createAction } from 'redux-actions';

class AsyncApp extends Component {
  componentDidMount() {
    const { dispatch, selectedPost } = this.props
    // 发出同步 Action
    dispatch(requestPosts(selectedPost));
    // 发出异步 Action
    dispatch(createAction(
      'FETCH_POSTS',
      fetch(`/some/API/${postTitle}.json`)
        .then(response => response.json())
    ));
  }
// 上面代码中,第二个 dispatch 方法发出的是异步 Action,只有等到操作结束,这个 Action 才会实际发出。注意,createAction 的第二个参数必须是一个 Promise 对象。

看一下 redux-promise 的源码,就会明白它内部是怎么操作的。

import isPromise from "is-promise";
import { isFSA } from "flux-standard-action";

export default function promiseMiddleware({ dispatch }) {
  return next => action => {
    if (!isFSA(action)) {
      return isPromise(action) ? action.then(dispatch) : next(action);
    }

    return isPromise(action.payload)
      ? action.payload
          .then(result => dispatch({ ...action, payload: result }))
          .catch(error => {
            dispatch({ ...action, payload: error, error: true });
            return Promise.reject(error);
          })
      : next(action);
  };
}

从上面代码可以看出,如果 Action 本身是一个 Promise,它 resolve 以后的值应该是一个 Action 对象,会被 dispatch 方法送出(action.then(dispatch)),但 reject 以后不会有任何动作;如果 Action 对象的 payload 属性是一个 Promise 对象,那么无论 resolve 和 reject,dispatch 方法都会发出 Action。

11、React-Redux 的用法

React-Redux 将所有组件分成两大类:

  • UI组件(presentational component)-- 负责 UI 的呈现
  • 容器组件(container component)-- 负责管理数据和逻辑

React-Redux 规定,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成。也就是说,用户负责视觉层,状态管理则是全部交给它。

11.1 安装

React Redux 依赖 React 0.14 或更新版本

```
npm install --save react-redux
```

11.2 connect()

React-Redux 提供 connect 方法,用于从 UI 组件生成容器组件。connect 的意思,就是将这两种组件连起来。
连接 React 组件与 Redux store。连接操作不会改变原来的组件类。反而返回一个新的已与 Redux store 连接的组件类。

import { connect } from "react-redux";
const VisibleTodoList = connect()(TodoList);

上面代码中,TodoList 是 UI 组件,VisibleTodoList 就是由 React-Redux 通过 connect 方法自动生成的容器组件。

但是,因为没有定义业务逻辑,上面这个容器组件毫无意义,只是 UI 组件的一个单纯的包装层。为了定义业务逻辑,需要给出下面两方面的信息。

  • 1、 输入逻辑:外部的数据(即 state 对象)如何转换为 UI 组件的参数
  • 2、 输出逻辑:用户发出的动作如何变为 Action 对象,从 UI 组件传出去。

因此,connect 方法的完整 API 如下。

import { connect } from "react-redux";

const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList);

上面代码中,connect 方法接受两个参数:mapStateToPropsmapDispatchToProps。它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将 state 映射到 UI 组件的参数(props),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。

11.3 mapStateToProps

mapStateToProps 是一个函数。它的作用就是像它的名字那样,建立一个从(外部的)state 对象到(UI 组件的)props 对象的映射关系。

const mapStateToProps = state => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter),
  };
};

const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case "SHOW_ALL":
      return todos;
    case "SHOW_COMPLETED":
      return todos.filter(t => t.completed);
    case "SHOW_ACTIVE":
      return todos.filter(t => !t.completed);
    default:
      throw new Error("Unknown filter: " + filter);
  }
};

mapStateToProps 会订阅 Store,每当 state 更新的时候,就会自动执行,重新计算 UI 组件的参数,从而触发 UI 组件的重新渲染。

mapStateToProps 的第一个参数总是 state 对象,还可以使用第二个参数,代表容器组件的 props 对象。

// 容器组件的代码
//    <FilterLink filter="SHOW_ALL">
//      All
//    </FilterLink>

const mapStateToProps = (state, ownProps) => {
  return {
    active: ownProps.filter === state.visibilityFilter,
  };
};

使用 ownProps 作为参数后,如果容器组件的参数发生变化,也会引发 UI 组件重新渲染。

connect 方法可以省略 mapStateToProps 参数,那样的话,UI 组件就不会订阅 Store,就是说 Store 的更新不会引起 UI 组件的更新。

11.4 mapDispatchToProps()

mapDispatchToProps 是 connect 函数的第二个参数,用来建立 UI 组件的参数到 store.dispatch 方法的映射。也就是说,它定义了哪些用户的操作应该当作 Action,传给 Store。它可以是一个函数,也可以是一个对象。

如果 mapDispatchToProps 是一个函数,会得到 dispatch 和 ownProps(容器组件的 props 对象)两个参数。

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    onClick: () => {
      dispatch({
        type: "SET_VISIBILITY_FILTER",
        filter: ownProps.filter,
      });
    },
  };
};

从上面代码可以看到,mapDispatchToProps 作为函数,应该返回一个对象,该对象的每个键值对都是一个映射,定义了 UI 组件的参数怎样发出 Action。

如果 mapDispatchToProps 是一个对象,它的每个键名也是对应 UI 组件的同名参数,键值应该是一个函数,会被当作 Action creator ,返回的 Action 会由 Redux 自动发出。举例来说,上面的 mapDispatchToProps 写成对象就是下面这样。

const mapDispatchToProps = {
  onClick: (filter) => {
    type: 'SET_VISIBILITY_FILTER',
    filter: filter
  };
}

11.5 <Provider> 组件

connect 方法生成容器组件以后,需要让容器组件拿到 state 对象,才能生成 UI 组件的参数。

一种解决方法是将 state 对象作为参数,传入容器组件。但是,这样做比较麻烦,尤其是容器组件可能在很深的层级,一级级将 state 传下去就很麻烦。

React-Redux 提供 Provider 组件,可以让容器组件拿到 state。

import { Provider } from "react-redux";
import { createStore } from "redux";
import todoApp from "./reducers";
import App from "./components/App";

let store = createStore(todoApp);

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
);

上面代码中,Provider 在根组件外面包了一层,这样一来,App 的所有子组件就默认都可以拿到 state 了。

它的原理是 React 组件的 context 属性

class Provider extends Component {
  getChildContext() {
    return {
      store: this.props.store,
    };
  }
  render() {
    return this.props.children;
  }
}

Provider.childContextTypes = {
  store: React.PropTypes.object,
};

store 放在了上下文对象 context 上面。然后,子组件就可以从 context 拿到 store

class VisibleTodoList extends Component {
  componentDidMount() {
    const { store } = this.context;
    this.unsubscribe = store.subscribe(() => this.forceUpdate());
  }

  render() {
    const props = this.props;
    const { store } = this.context;
    const state = store.getState();
    // ...
  }
}

VisibleTodoList.contextTypes = {
  store: React.PropTypes.object,
};

React-Redux 自动生成的容器组件的代码,就类似上面这样,从而拿到 store。

12、 异步实例

import React, { Component } from "react";
import PropTypes from "prop-types";
import ReactDOM from "react-dom";
import { createStore } from "redux";
import { Provider, connect } from "react-redux";

// UI 组件
class Counter extends Component {
  render() {
    const { value, onIncreaseClick } = this.props;
    return (
      <div>
        <span>{value}</span>
        <button onClick={onIncreaseClick}>Increase</button>
      </div>
    );
  }
}

Counter.propTypes = {
  value: PropTypes.number.isRequired,
  onIncreaseClick: PropTypes.func.isRequired,
};

// Action Creator
const increaseAction = { type: "increase" };

// 定义这个组件的 Reducer。
function counter(state = { count: 0 }, action) {
  const count = state.count;
  switch (action.type) {
    case "increase":
      return { count: count + 1 };
    default:
      return state;
  }
}

// 生成store对象
const store = createStore(counter);

// 定义value到state的映射
function mapStateToProps(state) {
  return {
    value: state.count,
  };
}

// 定义onIncreaseClick到dispatch的映射
function mapDispatchToProps(dispatch) {
  return {
    onIncreaseClick: () => dispatch(increaseAction),
  };
}

// 使用connect方法生成容器组件
const App = connect(
  mapStateToProps,
  mapDispatchToProps
)(Counter);

// 使用Provider在根组件外面包一层
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
);

13、 React-Router 路由库

使用 React-Router 的项目,与其他项目没有不同之处,也是使用 Provider 在 Router 外面包一层,毕竟 Provider 的唯一功能就是传入 store 对象。

const Root = ({ store }) => (
  <Provider store={store}>
    <Router>
      <Route path="/" component={App} />
    </Router>
  </Provider>
);

14、参考文档

15、扩展

猜你喜欢

转载自blog.51cto.com/8851255/2616851