前言
redux,相信前端的小伙伴们对此都不陌生吧。本文将以通俗易懂的语言攻坚这个框架,一方面希望帮助大家更方便的掌握这项技术,同时也希望我能够对个框架有一个更深的理解,废话就不多说,开干。
redux
众所周知,react是一个视图层的UI框架,对此,官网的描述为:React – A JavaScript library for building user interfaces。在一些小型的项目中,react框架可以通过自身的state和props属性完成数据交互。但是在一些企业级的大型web项目中,各组件间的通信是很复杂的,因为react本身是单向数据传递的,所以仅仅依靠自身的属性来实现数据状态管理,就显得心有余而力不足了。专业的事情需要专业的人来办。redux,就是一个专业的数据层框架,使用它可以更高效的完成组件间的通信,从而更好的管理数据状态。下面就一起走进redux的世界,让它告诉我们神马叫做专业。
redux核心概念
redux有三个核心概念。store,reducer,action,下面一一解释。何为store呢,谷歌翻译解释为商店。
├── node_modules
├── package.json
├── public
├── src
│ ├── App.css
│ ├── App.jsx
│ ├── index.css
│ └── index.js
└── yarn.lock
demo演示
首先,需要安装redux。
yarn add redux
下面就正式进入demo的环节。既然是去商店购物,所以第一步肯定是把这个商店建立,因此我们需要创建store。在src目录下创建store文件夹,并且新建index.js文件。
<!--文件目录:src/store/index.js-->
import { createStore } from "redux";
const store = createStore();
export default store;
这部分代码应该还是相对简单明了的,从redux库里面引入creatStore,然后就可以通过这个方法帮我们创建store。商店创建完毕后,第二个步骤就是将商品上架,这里的上架是指在视图页面上获取store中的数据,可以通过store的getState()方法来实现,来看下具体的代码。
<!--文件目录:src/App.jsx-->
constructor(props) {
super(props);
this.state = {
value: "",
goodsList: store.getState().goodsList
};
}
这样,就可以将store中的goodsList赋值给App组件的state,进而进行页面的渲染。好了,完成了上述的步骤,接下来是不是可以营业了呢?NO,当然不是,我们还需要营业员呢。好的,那么接下来就去雇佣营业员。同样的,在src目录下创建reducer文件夹,再新建index.js文件。下面是一个最简洁的reducer:
<!--文件目录:src/reducer/index.js-->
export default (state,action)=>{
return state;
}
reducer是一个纯函数,接收两个参数,state和action。redux是符合函数式编程的,使用了纯函数。简单说下纯函数,就是相同的输入,必然得到相同的输出,没有任何副作用的影响。举个🌰,在js数组方法中,有两个方法,slcie和splice,第一个就是纯函数,第二是就是不纯的。来来来,解释一波。
let group = [1,2,3,4,5,6,7];
group.slice(1,3);
group.slcie(1,3);
大家可以自己测试一下这段代码,可以发现,两次调用slcie方法的输入都为(1,3),结果输出都是[2,3],满足纯函数的定义。
let group = [1,2,3,4,5,6,7];
group.splice(1,3);
group.splice(1,3);
换成splice方法后,两次输入都为(1,3),但是第一次输出为[2,3,4],第二次输出为[5,6,7],不满足纯函数的定义。使用纯函数可以避免副作用带来的影响,大家以后可以多多尝试纯函数。好了,话题切回来,回到我们的reducer上,一般情况下,我们需要给reduce的第一个参数state赋一个初始值。
const defaultState = {
goodsList:[]
}
export default (state,action)=>{
return state;
}
这样,reducer就创建好了。下面就可以去商店报道啦。在store中引入reducer,并将reducer作为createStore的参数。
文件目录:src/store/index.js
import { createStore } from "redux";
import reducer from "../reducer"
const store = createStore(reducer);
export default store;
营业员也雇佣好了,下面就欢迎大家光临购物啦。我们先来看下最终demo的效果。主要的功能为,点击购买,将商品添加进列表以及点击取消购买,将商品从列表中移除。
<!--文件目录:src/App.jsx-->
import React, { Component } from "react";
import GoodsItem from "./goodsItem";
import store from "./store";
import { ADD_GOODS } from "./constant";
import "./App.css";
class App extends Component {
constructor(props) {
super(props);
this.state = {
value: "",
goodsList: store.getState().goodsList
};
}
/**
* 输入商品的名称
*/
handleChange = e => {
this.setState({
value: e.target.value
});
};
/**
* @description: 购买商品
* @param {string} 购买的商品名称
*/
addGoods = () => {
this.setState({
value: ""
});
store.dispatch({
type: ADD_GOODS,
value: this.state.value
});
};
render() {
const { goodsList = [], value } = this.state;
return (
<div className="App">
<h3>购物清单</h3>
<div className="header_wrapper">
<input
value={value}
placeholder="请输入商品名称"
onChange={this.handleChange}
/>
<button
onClick={() => {
this.addGoods();
}}
>
购买
</button>
</div>
<div>
{goodsList.length > 0
? goodsList.map((goodsItem, index) => {
return (
<GoodsItem
key={index}
goodsName={goodsItem.goodsName}
store={store}
/>
);
})
: null}
</div>
</div>
);
}
}
export default App;
点击购买按钮后,通过addGoods方法触发一个action。然后需要通过store的dispatch方法将这个action传递给reducer。
store.dispatch({ type: ADD_GOODS, value: this.state.value });
reducer接收到参数,将商品加入进购物列表。
import { ADD_GOODS, DELETE_GOODS } from "../constant";
const defaultState = {
goodsList: []
};
export default (state = defaultState, action) => {
switch (action.type) {
case ADD_GOODS:
return {
...state,
goodsList: [
...state.goodsList,
{
goodsName: action.value
}
]
};
case DELETE_GOODS:
return {
...state,
goodsList: state.goodsList.filter((item, index) => {
return item.goodsName !== action.value;
})
};
default:
return state;
}
};
reducer根据action的type属性,做出不同的处理。虽说是处理,但是不能直接去改变state的状态,因为state是不可变的,具有immutable属性,state的具体特性大家可以参考redux官网,这里就不展开叙述啦。我们可以通过解构的方式创建一个新的state,并对这个state进行相应处理,再将最新的状态返回给store。完成上述操作后,页面视图是没有发生变化的。store的状态并没有传递到页面,而需要通过store的subscribe()方法订阅数据状态的变化。只要这样,页面视图才会随着store中数据的变化而同步更新,。
<!--文件目录:src/App.js-->
<!--在App组件的constructor方法中,添加store.subscribe()方法-->
store.subscribe(() => {
this.setState({
goodsList: store.getState().goodsList
});
});
走到这一步,一个简单的购物demo就这样实现了。可能逻辑可能还是有点混乱,下面再将主要的知识点梳理一下。主要涉及三大概念,store、action、reducer。redux提供createStore()方法创建store。store用到了两个核心的api,getState()和dispatch。action是一个js对象,并且有一个type必选属性。reducer是一个纯函数,两个参数分别为state和action。执行流程为下图所示,是一个完成的闭环。
react-redux
看完了redux的执行流程,大部分人可能都有一个感受,复杂,借助汤家凤老师的一句话来说就是一点都不清爽。还好,业界大佬早就针对react这个特定的框架,对redux进行进一步升级,react-redux随着产生了,这样一切都会变得井然有序。 首先,我们需要在项目中安装这个神奇的库。
yarn add react-redux
Provider
react-redux提供了一个Provider组件,只有被Provider组件包裹的组件才可以访问到store中的数据。
<!--文件目录:src/index.js-->
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import store from "./store";
import App from "./App.jsx";
import "./index.css";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
根组件App被Provier组件包裹,这样项目中的所有组件都获取到store中的数据。
connect
connect是联系的意思。没错,这个方法的作用就是将store和当前的组件相关联。 参考react-redux官网,
function connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)
connect方法有4个可选参数,一般项目中只会用到前两个参数,并且这两个参数都为函数,具体我们根据代码来解释。
<!--文件目录:src/App.jsx-->
const mapStateToProps = state => {
return {
goodsList: state.goodsList
};
};
const mapDispatchToProps = dispatch => ({
addGoods: goodsName => dispatch({ type: ADD_GOODS, value: goodsName })
});
export default connect(mapStateToProps, mapDispatchToProps)(App);
上述代码中,connect方法的第一个参数mapStateToProps,该函数接收一个state参数,从state中获取store中的数据。然后在App组件中,就可以通过this.props.goodsList渲染购物列表。mapDispatchToProps方法的接收dispatch作为第一个参数,这个dispatch即为redux中的store.dispatch()方法,用来派发action。 使用react-reudx的Provider组件和connect方法,我们可以比较方便的去获取store中的数据,从而不再需要使用store.dispatch()去订阅store数据的变化。一旦store中的数据发生变化,页面视图也会随之改变。下面看下App.jsx的完整代码。
import React, { Component } from "react";
import { connect } from "react-redux";
import GoodsItem from "./goodsItem";
import store from "./store";
import { ADD_GOODS } from "./constant";
import "./App.css";
class App extends Component {
constructor(props) {
super(props);
this.state = {
value: ""
};
}
/**
* 输入商品的名称
*/
handleChange = e => {
this.setState({
value: e.target.value
});
};
/**
* @description: 购买商品
* @param {string} 购买的商品名称
*/
addGoods = () => {
this.props.addGoods(this.state.value);
this.setState({
value: ""
});
};
render() {
const { value } = this.state;
const goodsList = this.props.goodsList;
return (
<div className="App">
<h3>购物清单</h3>
<div className="header_wrapper">
<input
value={value}
placeholder="请输入商品名称"
onChange={this.handleChange}
/>
<button
onClick={() => {
this.addGoods();
}}
>
购买
</button>
</div>
<div>
{goodsList.length > 0
? goodsList.map((goodsItem, index) => {
return (
<GoodsItem
key={index}
goodsName={goodsItem.goodsName}
store={store}
/>
);
})
: null}
</div>
</div>
);
}
}
const mapStateToProps = state => {
return {
goodsList: state.goodsList
};
};
const mapDispatchToProps = dispatch => ({
addGoods: goodsName => dispatch({ type: ADD_GOODS, value: goodsName })
});
export default connect(mapStateToProps, mapDispatchToProps)(App);
goodsItem完整代码
import React, { Component } from "react";
import { connect } from "react-redux";
import { DELETE_GOODS } from "./constant";
class GoodsItem extends Component {
cancel = () => {
this.props.cancel(this.props.goodsName);
};
render() {
const { goodsName } = this.props;
return (
<div className="contentItem">
<div>{goodsName}</div>
<div onClick={this.cancel}>取消购买</div>
</div>
);
}
}
const mapStateToProps = () => {
return {};
};
const mapDispatchToProps = dispatch => ({
addGoods: goodsName => dispatch({ type: DELETE_GOODS, value: goodsName })
});
export default connect(mapStateToProps, mapDispatchToProps)(GoodsItem);
Redux DevTools
为了更好的观察redux的数据状态,我们可以使用chrome的一款插件,Redux DevTools 。要正常使用这款插件,我们还需要稍微修改下我们的项目代码。更多细节可以参考官网文档。
<!--文件目录:src/store/index.js-->
import { createStore } from "redux";
import reducer from "../reducer";
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
export default store;
给createStore增加一个参数,这个参数的也比较好理解,如果检测到当前当前浏览器有安装Redux DevTools,就启用该插件,否则按正常代码执行。
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
重新启动我们的项目。打开开发者工具,我们可以发现多了一个redux。
结语
redux的知识点还是比较复杂的,而且是符合函数式编程的,如果不经常使用,有些概念难免会有些遗忘。写文章的时候,其实是花了很多的时间,因为我也在不停的理思路,想以一种最通俗易懂的语言把redux讲明白,让没怎么使用过redux的前端人能快速上手这个框架。因为我自己的项目中其实一直是在用mobx,所以,撰写本文,也让我对redux有了更多的认识。文章中难免有一些不严谨或者错误的地方,希望大家可以积极指出告诉我。有关redux其实还有很多可以说,我也会积极整理总结,在以后的文章中和大家分享,加油!
参考
- 《深入浅出React和redux》程墨著
- React-Redux 中文文档
- React16.4开发简书项目从零基础入门到实战
- redux官网
- react-redux官网