注:这篇是16年10月的文章,搬运自本人 blog...javascript
参考资料前端
首先要明确一点,虽然 redux 是由 flux 演变而来,但咱们彻底能够而且也应该抛开 react 进行学习,这样能够避免一开始就陷入各类细节之中。java
因此推荐使用 jsbin 进行调试学习,或者使用 create-react-app 做为项目脚手架。node
Redux is a predictable state container for JavaScript apps.
Redux 是一个 JavaScript 状态容器,提供可预测化的状态管理。react
先不要在乎那些细节git
纯函数:简单的说就是对于一样的输入老是返回一样的输出,而且没有反作用的函数。(推荐学习了解下函数式编程)es6
- 随着 JavaScript 单页应用开发日趋复杂,JavaScript 须要管理比任什么时候候都要多的 state (状态)。 这些 state 可能包括服务器响应、缓存数据、本地生成还没有持久化到服务器的数据,也包括 UI 状态,如激活的路由,被选中的标签,是否显示加载动效或者分页器等等。
- 管理不断变化的 state 很是困难。若是一个 model 的变化会引发另外一个 model 变化,那么当 view 变化时,就可能引发对应 model 以及另外一个 model 的变化,依次地,可能会引发另外一个 view 的变化。直至你搞不清楚到底发生了什么。state 在何时,因为什么缘由,如何变化已然不受控制。 当系统变得错综复杂的时候,想重现问题或者添加新功能就会变得举步维艰。
- 若是这还不够糟糕,考虑一些来自前端开发领域的新需求,如更新调优、服务端渲染、路由跳转前请求数据等等。前端开发者正在经受史无前例的复杂性,难道就这么放弃了吗?固然不是。
- 这里的复杂性很大程度上来自于:咱们老是将两个难以厘清的概念混淆在一块儿:变化和异步。 我称它们为曼妥思和可乐。若是把两者分开,能作的很好,但混到一块儿,就变得一团糟。一些库如 React 试图在视图层禁止异步和直接操做 DOM 来解决这个问题。美中不足的是,React 依旧把处理 state 中数据的问题留给了你。Redux就是为了帮你解决这个问题。
- 跟随 Flux、CQRS 和 Event Sourcing 的脚步,经过限制更新发生的时间和方式,Redux 试图让 state 的变化变得可预测。这些限制条件反映在 Redux 的 三大原则中。
简单总结就是使用 Redux 咱们就能够没有蛀牙(大雾)github
整个应用的 state 被储存在一棵 object tree 中,而且这个 object tree 只存在于惟一一个 store 中。编程
唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
由于全部的修改都被集中化处理,且严格按照一个接一个的顺序执行,(dispatch 同步调用 reduce 函数)所以不用担忧 race condition 的出现。 Action 就是普通对象而已,所以它们能够被日志打印、序列化、储存、后期调试或测试时回放出来。
为了描述 action 如何改变 state tree ,你须要编写 reducer。
Reducer 只是纯函数,它接收先前的 state 和 action,并返回新的 state。刚开始你能够只有一个 reducer,随着应用变大,你能够把它拆成多个小的 reducers,分别独立地操做 state tree 的不一样部分。
Action 就是一个普通的 JavaScript Object。
redux 惟一限制的一点是必须有一个 type 属性用来表示执行哪一种操做,值最好用字符串,而不是 Symbols,由于字符串是可被序列化的。
其余属性用来传递这次操做所需传递的数据,redux 对此不做限制,可是在设计时能够参照 Flux 标准 Action。
简单总结 Flux Standard action 就是
- 一个 action 必须是一个 JavaScript Object,而且有一个 type 属性。
- 一个 action 能够有 payload/error/meta 属性。
- 一个 action 不能有其余属性。
Reducer 的工做就是接收旧的 state 和 action,返回新的 state。
(previousState, action) => newState
之因此称做 reducer 是由于它将被传递给 Array.prototype.reduce(reducer, ?initialValue)
方法。保持 reducer 纯净很是重要。永远不要在 reducer 里作这些操做:
Store 就是用来维持应用全部的 state 树的一个对象。
在 redux 中只有一个 store(区别于 flux 的多个 store),在 store 中保存全部的 state,能够把它当成一个封装了 state 的类。而除了对其 dispatch 一个 action 之外没法改变内部的 state。
在实际操做中咱们只须要把根部的 reducer 函数传递给 createStore 就能够获得一个 store。
import { createStore } from 'redux';
function reducer(state, action) {
switch (action.type) {
case 'SOME_ACTION':
// 一些操做
return newState; // 返回新状态
default:
return state;
}
}
const store = createStore(reducer);
复制代码
redux 中提供了这几个 api 操做 store
返回当前的整个 state 树。
分发 action 给对应的 reducer。
该函数会调用 getState() 和传入的 action 以【同步】的方式调用 store 的 reduce 函数,而后返回新的 state。从而 state 获得了更新,而且变化监听器(change listener)会被触发。(对于异步操做则将其放到了 action creator 这个步骤)
为 store 添加一个变化监听器,每当 dispatch 的时候就会执行,你能够在 listener(回调函数)中使用 getState() 来获得当前的 state。
这个 api 设计的挺有意思,它会返回一个函数,而你执行这个函数后就能够取消订阅。
替换 store 当前用来计算 state 的 reducer。
这是一个高级 API。只有在你须要实现代码分隔,并且须要当即加载一些 reducer 的时候才可能会用到它。在实现 Redux 热加载机制的时候也可能会用到。
忽略各类类型判断,实现一个最简的 createStore 能够用如下代码。参考资料
const createStore = (reducer) => {
let state;
let listeners = [];
const getState = () => state;
const dispatch = (action) => {
state = reducer(state, action); // 调用 reducer
listeners.forEach(listener => listener()); // 调用全部变化监听器
};
const subscribe = (listener) => {
listeners.push(listener);
return () => {
// 返回解除监听函数
listeners = listeners.filter(l => l !== listener);
};
}
dispatch({}); // 初始化
return { getState, dispatch, subscribe };
};
复制代码
实现一样功能的 Counter
{% iframe jsbin.com/qalevu/edit… 100% 800 %}
在添加 react-redux 以前,为了体会下 react-redux 的做用,首先来实现一个比计数器更复杂一点儿的 TodoApp 栗子~
组件通常分为
- | 容器组件 | 展现组件 |
---|---|---|
Location | 最顶层,路由处理 | 中间和子组件 |
Aware of Redux | 是 | 否 |
读取数据 | 从 Redux 获取 state | 从 props 获取数据 |
修改数据 | 向 Redux 派发 actions | 从 props 调用回调函数 |
最佳实践通常是由容器组件负责一些数据的获取,进行 dispatch 等操做。而展现组件组件不该该关心逻辑,全部数据都经过 props 传入。
这样才能达到展现组件能够在多处复用,在具体复用时就是经过容器组件将其包装,为其提供所需的各类数据。
// 暂且使用数字做为 id
let nextTodoId = 0;
/*-- action creators --*/
const addTodo = (text) => (
{ type: 'ADD_TODO', id: nextTodoId++, text }
);
const toggleTodo = (id) => (
{ type: 'TOGGLE_TODO', id }
);
const setVisibilityFilter = (filter) => (
{ type: 'SET_VISIBILITY_FILTER', filter }
);
复制代码
// 默认初始状态
const initialState = { filter: 'SHOW_ALL', todos: [] };
function rootReducer(state = initialState, action) {
switch (action.type) {
case 'ADD_TODO':
// 对象解构
const { id, text } = action;
return {
...state,
todos: {
...state.todos,
{ id, text, completed: false },
},
};
case 'TOGGLE_TODO':
return {
...state,
todos: state.todos.map(todo => {
if (todo.id !== action.id) return todo;
return {
...todo,
completed: !todo.completed,
};
}),
};
case 'SET_VISIBILITY_FILTER':
return {
...state,
filter: action.filter,
};
default:
return state;
}
}
复制代码
注意!
- 不要直接修改原有的 state,而是返回一个新的 state。可使用 Object.assign() 新建一个新的 state。不能这样使用 Object.assign(state, { visibilityFilter: action.filter }),由于它会改变第一个参数的值。你必须把第一个参数设置为空对象。你也能够开启对 ES7 提案对象展开运算符的支持, 从而使用 { ...state, ...newState } 达到相同的目的。
- 在 default 的状况下返回旧的 state,用来兼容遇到未知的 action 这样的错误。
拆分 reducer 目前代码看着比较冗长,其实在逻辑上 todos 的处理和 filter 的处理应该分开,因此在 state 没有互相耦合时,能够将其拆分,从而让 reducer 精细地对于对应 state 的子树进行处理。
// 处理单个 todo
const todoReducer = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
id: action.id,
text: action.text,
completed: false,
};
case 'TOGGLE_TODO':
if (state.id !== action.id) return state;
return {
...state,
completed: !state.completed,
};
default:
return state;
}
};
// 处理 todos
const todosReducer = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
todoReducer(undefined, action),
];
case 'TOGGLE_TODO':
return state.map(t => todoReducer(t, action));
default:
return state;
};
};
// 处理 filter
const filterReducer = (state = 'SHOW_ALL', action) => {
switch (action.type) {
case 'SET_VISIBILITY_FILTER':
return action.filter;
default:
return state;
};
};
const rootReducer = (state = initialState, action) => ({
todos: todosReducer(state.todos, action),
filter: filterReducer(state.filter, action),
});
复制代码
注意观察最后的 rootReducer 函数,返回的是一个通过各类 reducer 处理过并合并后的新 state。
然鹅,注意这里 todos: todos(state.todos, action),
传入 state.todos,返回的必定也是 todos(由于都是 state 树上的节点)。
因此 redux 提供了很实用的 combineReducers
api,用于简化 reducer 的合并。
import { combineReducers } from 'redux';
const rootReducer = combineReducers({
todos: todosReducer,
filter: filterReducer,
});
// initialState 能够做为第二个参数传入
const store = createStore(rootReducer, initialState);
复制代码
而且若是 reducer 与 state 节点同名的话(即 todosReducer -> todos)还能经过 es6 的语法更进一步地简化
import { combineReducers } from 'redux';
const rootReducer = combineReducers({ todos, filter });
// initialState 能够做为第二个参数传入
const store = createStore(rootReducer, initialState);
复制代码
随着应用的膨胀,咱们还能够将拆分后的 reducer 放到不一样的文件中, 以保持其独立性并用于专门处理不一样的数据域。
首先只写一个根组件 <TodoApp />
,store 经过 props 传入 TodoApp,并在生命周期的 componentDidMount 和 componentWillUnmount 时分别订阅与取消订阅。
import React, { Component } from 'react';
class TodoApp extends Component {
// 订阅 store 的变化
componentDidMount() {
const { store } = this.props;
this.unsubscribe = store.subscribe(
this.forceUpdate.bind(this)
);
}
// 取消订阅
componentWillUnmount() {
this.unsubscribe();
}
// 渲染单个 todo
_renderTodo(todo) {
const { store } = this.props;
return (
<li key={todo.id} onClick={() => store.dispatch(toggleTodo(todo.id))} style={{ textDecoration: todo.completed ? 'line-through' : 'none', cursor: todo.completed ? 'default' : 'pointer', }} > {todo.text} </li>
);
}
// 根据当前 filter 是否匹配,返回字符串或是 a 连接
_renderFilter(renderFilter, name) {
const { store } = this.props;
const { filter } = store.getState();
if (renderFilter === filter) return name;
return (
<a href='#' onClick={e => { e.preventDefault(); store.dispatch(setVisibilityFilter(renderFilter)) }}> {name} </a>
);
}
// 根据当前 filter 过滤须要渲染的 todos
_getVisibleTodos(todos, filter) {
switch (filter) {
case 'SHOW_ALL':
return todos;
case 'SHOW_COMPLETED':
return todos.filter(todo => todo.completed);
case 'SHOW_ACTIVE':
return todos.filter(todo => !todo.completed);
default:
return todos;
}
}
render() {
const { store } = this.props;
const { todos, filter } = store.getState();
let input;
return (
<div> {/* AddTodo */} <input type="text" ref={node => input = node} /> <button onClick={() => { if (!input.value) return; store.dispatch(addTodo(input.value)); input.value = ''; }}> addTodo </button> {/* TodoList */} <ul> {this._getVisibleTodos(todos, filter) .map(this._renderTodo.bind(this)) } </ul> {/* Footer */} <p> Show: {' '} {this._renderFilter('SHOW_ALL', 'all')} {', '} {this._renderFilter('SHOW_COMPLETED', 'completed')} {', '} {this._renderFilter('SHOW_ACTIVE', 'active')} </p> </div> ); } } 复制代码
TodoApp 只有根组件 {% iframe jsbin.com/bodise/edit… 100% 800 %}
将全部界面内容全写在 TodoApp 中实在是太臃肿了,接下来根据以前的分析结果将其分为如下子组件(全是展现组件)
const AddTodo = ({ onAddClick }) => {
let input;
return (
<div>
<input type="text" ref={node => input = node} />
<button onClick={() => {
onAddClick(input.value);
input.value = '';
}}>
addTodo
</button>
</div>
);
};
const Todo = ({ text, onClick, completed }) => (
<li
onClick={onClick}
style={{
textDecoration: completed
? 'line-through'
: 'none',
cursor: completed
? 'default'
: 'pointer',
}}
>
{text}
</li>
);
const TodoList = ({ todos, onTodoClick }) => (
<ul>
{todos.map(todo =>
<Todo
key={todo.id}
{...todo}
onClick={() => onTodoClick(todo.id)}
/>
)}
</ul>
);
const FilterLink = ({ filter, onClick, renderFilter, children }) => {
if (renderFilter === filter) return (<span>{children}</span>);
return (
<a href='#' onClick={e => {
e.preventDefault();
onClick(renderFilter);
}}>
{children}
</a>
);
};
const Footer = ({ filter, onFilterClick }) => (
<p>
Show:
{' '}
<FilterLink
filter={filter}
renderFilter="SHOW_ALL"
onClick={onFilterClick}
>
all
</FilterLink>
{', '}
<FilterLink
filter={filter}
renderFilter="SHOW_COMPLETED"
onClick={onFilterClick}
>
completed
</FilterLink>
{', '}
<FilterLink
filter={filter}
renderFilter="SHOW_ACTIVE"
onClick={onFilterClick}
>
active
</FilterLink>
</p>
);
复制代码
因此 TodoApp 精简后是这样~
class TodoApp extends Component {
// ...
render() {
const { store } = this.props;
const { todos, filter } = store.getState();
return (
<div>
<AddTodo
onAddClick={text => {
if (!text) return;
store.dispatch(addTodo(text));
}}
/>
<TodoList
todos={this._getVisibleTodos(todos, filter)}
onTodoClick={id => store.dispatch(toggleTodo(id))}
/>
<Footer
filter={filter}
onFilterClick={filter => {
store.dispatch(setVisibilityFilter(filter));
}}
/>
</div>
);
}
}
复制代码
如今咱们仍然是以 TodoApp 做为容器组件,其中各个子组件都是展现组件。
可是这样作的话一旦子组件须要某个属性,就须要从根组件层层传递下来,好比 FilterLink 中的 filter 属性。
因此下面咱们增长容器组件,让展现组件经过容器组件得到所需属性。
// store.dispatch 又被放回来了,
// 由于暂时咱们只在 AddTodo 组件中使用 addTodo 这个 action
// 之后增长了新的 form 以后能够考虑再将 store.dispatch 移出去
const AddTodo = ({ store }) => {
let input;
return (
<div>
<input type="text" ref={node => input = node} />
<button onClick={() => {
if (!input.value) return;
store.dispatch(addTodo(input.value));
input.value = '';
}}>
addTodo
</button>
</div>
);
};
const Todo = ({ text, onClick, completed }) => (
<li
onClick={onClick}
style={{
textDecoration: completed
? 'line-through'
: 'none',
cursor: completed
? 'default'
: 'pointer',
}}
>
{text}
</li>
);
const TodoList = ({ todos, onTodoClick }) => (
<ul>
{todos.map(todo =>
<Todo
key={todo.id}
{...todo}
onClick={() => onTodoClick(todo.id)}
/>
)}
</ul>
);
// 容器组件
class VisibleTodoList extends Component {
// 订阅 store 的变化
componentDidMount() {
const { store } = this.props;
this.unsubscribe = store.subscribe(
this.forceUpdate.bind(this)
);
}
// 取消订阅
componentWillUnmount() {
this.unsubscribe();
}
// 根据当前 filter 过滤须要渲染的 todos
_getVisibleTodos(todos, filter) {
switch (filter) {
case 'SHOW_ALL':
return todos;
case 'SHOW_COMPLETED':
return todos.filter(todo => todo.completed);
case 'SHOW_ACTIVE':
return todos.filter(todo => !todo.completed);
default:
return todos;
}
}
render() {
const { store } = this.props;
const { todos, filter } = store.getState();
return (
<TodoList
todos={this._getVisibleTodos(todos, filter)}
onTodoClick={id => {
store.dispatch(toggleTodo(id))
}}
/>
);
}
}
// 本来的 FilterLink 改为 Link,去掉 filter 和 renderFilter 属性,改成传入 active
const Link = ({ active, onClick, children }) => {
if (active) return (<span>{children}</span>);
return (
<a href='#' onClick={e => {
e.preventDefault();
onClick();
}}>
{children}
</a>
);
};
// 容器组件
class FilterLink extends Component {
// 订阅 store 的变化
componentDidMount() {
const { store } = this.props;
this.unsubscribe = store.subscribe(
this.forceUpdate.bind(this)
);
}
// 取消订阅
componentWillUnmount() {
this.unsubscribe();
}
render() {
const { store, renderFilter, children } = this.props;
const { filter } = store.getState();
return (
<Link
active={filter === renderFilter}
onClick={() => store.dispatch(
setVisibilityFilter(renderFilter)
)}
>
{children}
</Link>
);
}
}
// 展现组件
const Footer = ({ store }) => (
<p>
Show:
{' '}
<FilterLink
store={store}
renderFilter="SHOW_ALL"
>
all
</FilterLink>
{', '}
<FilterLink
store={store}
renderFilter="SHOW_COMPLETED"
>
completed
</FilterLink>
{', '}
<FilterLink
store={store}
renderFilter="SHOW_ACTIVE"
>
active
</FilterLink>
</p>
);
// 在不使用全局变量 store 的状况下,
// 暂时只能经过 props 传递进来,
// Don't worry~很快就不会这么麻烦了~
const TodoApp = ({ store }) => (
<div>
<AddTodo store={store} />
<VisibleTodoList store={store} />
<Footer store={store} />
</div>
);
复制代码
经过观察重构后的代码能够发现有三点麻烦的地方
_getVisibleTodos
函数让咱们先来解决第一个麻烦~,利用 React 提供的 context 特性
class Provider extends Component {
// 经过该方法向 children 的 context 注入 store
getChildContext() {
return { store: this.props.store };
}
render() {
return this.props.children;
}
}
// 必需要声明传入 context 的 store 的类型
Provider.childContextTypes = {
store: React.PropTypes.object,
};
复制代码
自顶向下地看一下如何使用到 TodoApp 中
// 1. 使用 Provider 包裹 TodoApp,并将 store 做为 props 传入
ReactDOM.render(
<Provider store={createStore(rootReducer, initialState)}> <TodoApp /> </Provider>,
document.getElementById('container'),
);
// 2. 根组件 TodoApp: 和 store say goodbye~,
// 由于 TodoApp 并非容器组件~
const TodoApp = () => (
<div> <AddTodo /> <VisibleTodoList /> <Footer /> </div>
);
// 3. AddTodo: 因为 props 固定做为第一个传入子组件的参数,
// 因此 { store } 要声明在第二位,然鹅须要声明 contextTypes...
const AddTodo = (props, { store }) => {
// ...
};
// 必须声明
AddTodo.contextTypes = {
store: React.PropTypes.object,
};
// 4. VisibleTodoList: 从 props 改为从 context 中获取 store,
// 一样声明 contextTypes...
class VisibleTodoList extends Component {
// 订阅 store 的变化
componentDidMount() {
const { store } = this.context; // props -> context
// ...
}
// ...
render() {
const { store } = this.context; // props -> context
const { todos, filter } = store.getState();
// ...
}
}
// 必须声明
VisibleTodoList.contextTypes = {
store: React.PropTypes.object,
};
// -- TodoList 和 Todo 不变 --
// 5. Footer:和 store say goodbye...
const Footer = () => (
<p> Show: {' '} <FilterLink renderFilter="SHOW_ALL"> all </FilterLink> {', '} <FilterLink renderFilter="SHOW_COMPLETED"> completed </FilterLink> {', '} <FilterLink renderFilter="SHOW_ACTIVE"> active </FilterLink> </p>
);
// 6. FilterLink: 同 VisibleTodoList(props + contextTypes...)
class FilterLink extends Component {
// 订阅 store 的变化
componentDidMount() {
const { store } = this.context; // props -> context
// ...
}
// ...
render() {
const { renderFilter, children } = this.props;
const { store } = this.context; // props -> context
const { filter } = store.getState();
// ...
}
}
// 必须声明
FilterLink.contextTypes = {
store: React.PropTypes.object,
};
// -- Link 不变 --
复制代码
如今中间的非容器组件彻底不用为了本身的孩子而费劲地传递 store={store} 因此以上咱们就实现了简化版的由 react-redux 提供的第一个组件 <Provider />
。
然鹅,有木有以为老写 contextTypes 好烦啊,并且 context 特性并不稳定,因此 context 并不该该直接写在咱们的应用代码里。
计将安出?
恭喜你~面向对象的思想学的很不错~
虽然 JavaScript 底层各类东西都是面向对象,然而在前端一旦与界面相关,照搬面向对象的方法实现起来会很麻烦...
做为 react 亲生的 mixin 确实在多组件间共享方法提供了一些便利,然而使用 mixin 的组件须要了解细节,从而避免状态污染,因此一旦 mixin 数量多了以后会愈来愈难维护。
Unfortunately, we will not launch any mixin support for ES6 classes in React. That would defeat the purpose of only using idiomatic JavaScript concepts.
因此官方也放弃了在 ES6 class 中对 mixin 的支持。
hocFactory:: W: React.Component => E: React.Component
如上所示 hoc 的构造函数接收一个 W(表明 WrappedComponent)返回一个 E(表明 Enhanced Component),而 E 就是这个高阶组件。
假设咱们有一个旧组件 Comp,然鹅如今接收参数有些变更。
固然你能够复制粘贴再修改旧组件的代码...(大侠受窝一拜)
也能够这么写,返回一个新组件来包裹旧组件。
class NewComp extends Component {
mapProps(props) {
return {/* new props */};
}
render() {
return (<Comp {...this.mapProps(this.props)} />); } } 复制代码
然鹅,若是有一样逻辑的更多的组件须要适配呢???总不能有几个抄几遍吧...
因此骚年你据说太高阶组件么~?
// 先返回一个函数,而那个函数再返回新组件
const mapProps = mapFn => Comp => {
return class extends Component {
render() {
return (<Comp {...this.mapProps(this.props)} />); } }; }; const NewComp = mapProps(mapFn)(Comp); // 注意调用了两次 复制代码
能够看到借助高阶组件咱们将 mapFn 和 Comp 解耦合,这样就算须要再嵌套多少修改逻辑都没问题~天黑都不怕~
ok,扯了这么多的淡,终于要说到 connect 了 是哒,你木有猜错,react-redux 提供的第二个也是最后一个 api —— connect 返回的就是一个高阶组件。
使用的时候只须要 connect()(WrappedComponent)
返回的 component 自动就完成了在 componentDidMount 中订阅 store,在 componentWillUnmount 中取消订阅和声明 contextTypes。
这样就只剩下最后一个麻烦
3.应用其实并不须要渲染全部的 todos,因此内部很麻烦地定义了
_getVisibleTodos
函数
其实 connect 函数的第一个参数叫作 mapStateToProps,做用就是将 store 中的数据提早处理或过滤后做为 props 传入内部组件,以便内部组件高效地直接调用。这样最后一个麻烦也解决了~
然鹅,咱们问本身这样就够了么?并无...
还有最后一个细节,以 FilterLink 为例。
class FilterLink extends Component {
// ...
render() {
const { store, renderFilter, children } = this.props;
const { filter } = store.getState();
return (
<Link active={filter === renderFilter} onClick={() => store.dispatch( setVisibilityFilter(renderFilter) )} > {children} </Link>
);
}
}
复制代码
除了从 store 中获取数据(filter),咱们还从中获取了 dispatch,以便触发 action。若是将回调函数 onClick 的内容也加到 props 中,那么借助 connect 整个 FilterLink 的逻辑岂不是都被咱们抽象完了?
是哒,connect 的第二个参数叫作 mapDispatchToProps,做用就是将各个调用到 dispatch 的地方都抽象成函数加到 props 中的传给内部组件。这样最后一个麻烦终于真的被解决了~
const mapStateToLinkProps = (state, ownProps) => ({
// ownProps 是原组件的 props,
// 这里为了和高阶组件的 props 区分
active: ownProps.renderFilter === state.filter,
});
const mapDispatchToLinkProps = (dispatch, ownProps) => ({
onClick: () => {
dispatch(
setVisibilityFilter(ownProps.renderFilter)
);
},
});
// 注意原 FilterLink 整个都被咱们删了
const FilterLink = connect(
mapStateToLinkProps,
mapDispatchToLinkProps
)(Link);
复制代码
TodoApp 使用 react-redux {% iframe jsbin.com/fumihi/edit… 100% 800 %}