书接上回 《redux懒人教程》,我们已经学会了使用redux管理混乱的全局状态,下面我们一起看看redux是怎么帮助我们管理react应用的状态的。
react面临什么问题
我们先捋一捋react的一些基础概念,看看在什么情况下我们才需要引入redux。
react这样的前端框架的功能一言概括就是实现了数据和UI的实时映射。开发者只需要通过react组件管理数据,当数据变化时,react会让其映射的UI实时更新。而组件的数据主要是两个:state和props。props是父组件传递过来的数据,state是组件自身管理的数据,它又以props的形式传递给子组件。一个组件的props和state的变化都会引发该组件及其子组件对应UI的变化,也就是重渲染。
props更像数据管道,state则比较像数据引擎。
现在问题来了,组件4和组件7之间要怎么通信呢。全体起立,让我们再次大声喊出这一句:
在计算机领域,如果有什么事是加一层中间层解决不了的,那就加两层
只需要把组件4和组件7需要通信的数据提升到组件1上作为组件1的state来管理,再通过props层层传递给组件4和组件7就行了。 OK,现在组件树只有三层,通过这样的方式或许还可以接受,但是当组件树有很多层的时候,你岂不是得写很多次这样的props传递的逻辑,这实在是太过辛苦而不优雅的一件事情了。因此,我们也需要引入一个类似全局变量的东西,而react提供的context就是这个全局变量,它允许底层组件直接跨过中间组件,读取顶层组件的数据,像这样:
react-redux是怎么解决问题的
结合之前的内容,我们明确:
- redux是用来管理全局状态的
- react的全局状态被放在顶层组件的context中
那么现在你应该能解答这个这个灵魂之问了:要把redux装进react,总共分几步?
答:三步:
- 第一步:制定数据管理规则并生成规则执行者,也就是设计reducer和action,生成store;
- 第二步:将store放在react应用顶层组件的context中;
- 第三步:react底层组件从context中取出store,对数据进行读写操作
我们延续上一篇文章中管理一个数组的例子:
第一步,我们已经完成了:
import { createStore } from 'redux';
const initState = [];
const reducer = (state = initState, action) => {
switch(action.type) {
case 'add':
return [...state, action.addedItem];
case 'delete':
return [...state.slice(0, action.deletedIndex), ...state.slice(action.deletedIndex + 1)];
case 'clear':
return []
default:
return state;
}
};
const store = createStore(reducer);
第二步,把store放进顶层组件的context中,这个顶层组件就是react-redux中的Provider组件:
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import App from './App.js';
const initState = [];
const reducer = (state = initState, action) => {
switch(action.type) {
case 'add':
return [...state, action.addedItem];
case 'delete':
return [...state.slice(0, action.deletedIndex), ...state.slice(action.deletedIndex + 1)];
case 'clear':
return []
default:
return state;
}
};
const store = createStore(reducer);
ReactDOM.render(<Provider store={store}>
<App />
</Provider>,
document.getElementById('root');
第三步,底层组件从context中取出store对状态进行读写。完成这一步时react-redux又把这个底层组件细分成一父一子两个组件,首先由一个父组件专门负责从context中取出store,把store管理的顶层状态放在自己的数据引擎state中,用store.subcribe监听顶层状态的变化,顶层状态一改变,这个父组件的数据引擎state就跟着改变,然后再把数据通过管道props传递给子组件。脏活累活都被父组件干了,我们开发子组件的时候只需要享用父组件传递过来的props就好了,真是父爱如山啊。另外,对顶层状态进行操作的函数dispatch也需要通过props传递给子组件,这样子组件才可以修改顶层状态嘛。思路已经理通了,react-redux还要做最后一点优化——因为父组件完全没必要直接把store管理的状态整个传给子组件,也没必要把dispatch函数本身传递给子组件。假设顶层组件管理了100多个状态,而实际上你在开发的底层组件可能只想读写其中的一个状态而已——比如这个状态叫theme,那么我希望我写底层组件时,完全不用管其他状态是什么,也不用管要修改其他状态需要dispatch什么action,我只想通过props.theme读状态,通过props.setTheme()写状态。嗨呀其实这不就是加一个中间层就能搞定的事吗,而react-redux需要你写的mapStateToProps函数和mapDispatchToProps函数就是专门用来干这个的。
好了啰里啰嗦一大堆,上面的这一段是告诉你要完成第三步都需要做什么,这其中像从context中取出store这种又脏又累的重复性工作,已经被react-redux在connect这个高阶组件中做完了。所谓高阶组件,其实就是一个函数,传给它一个组件,它返回一个新的组件。在connect这个高阶组件中,你需要传给他mapStateToProps,mapDispatchToProps以及你的业务子组件,它返回给你一个能读写store管理的状态的组件。
talk is cheap, show you the code好伐:
import React from 'react';
import {connect} from 'react-redux';
const mapStateToProps = (state) => {
return {
array: state
}
};
const mapDispatchToProps = (dispatch) => {
return {
addItem: (item) => {
dispatch({
action: 'add',
addedItem: item
});
}
}
};
const App = (props) => {
return (
<div>
<div>{props.array}</div>
<div>
<button onClick = {props.addItem(1)}>在数组中添加一个1</button>
<div>
<div>
)
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
总结
最后,把react-redux的使用方法用下面这个图概括一下:
好了,到现在为止redux的懒人教程系列就算完结了,笔者在写的过程中一直极力避免去详细解析API的具体用法,或者太过深入地解释原理,主要是希望能帮助读者花比较小的代价就能大致理解为什么要用redux/react-redux, 怎么用redux/react-redux。 redux的主要生态是其丰富的中间件,也有实现相似功能的状态管理工具mobx可以学习,在react-hook风头正盛之际,react-redux也提供了对应的hook api,在此就不再一一介绍了,搞懂redux核心用法之后上手这些都会很快的。
水平有限,有哪些写的不清不楚或者表述不正确的地方欢迎多交流指正,觉得有帮助的话求一个赞哦 :)