先来看看 React 一些特点和没有解决的问题:
- 通信:组件之间如何通信?react 采用传参,对于大应用,很不方便。
- 数据流:数据如何和视图串联起来?路由和数据如何绑定?如何编写异步逻辑?等等
为了解决这些问题引入 redux,前几天看见一个博客,把 redux 的原理结合动图把为什么使用 redux 讲的特别透彻。
一定要看:Redux设计思想与使用场景
同时配上这篇:redux 设计思想
总结一下就是:
redux 的诞生是为了给 React 应用提供「可预测化的状态管理」机制。
Redux 会将整个应用状态(其实也就是数据)存储到到一个地方,称为 store。
这个 store 里面保存一棵状态树(state tree)
组件改变 state 的唯一方法是通过调用 store 的 dispatch 方法,触发一个 action,这个action 被对应的 reducer 处理,于是 state 完成更新。
组件可以派发 (dispatch) 行为 (action) 给 store,而不是直接通知其它组件
其它组件可以通过订阅 store 中的状态 (state) 来刷新自己的视图
一、初识 Redux
网站重要的开门注:
redux a predictable state container for JavaScript apps.
Redux是一个可预测状态容器。
为了便于直观理解,演示先不配合 React 使用,全部在一个文件 1.js
里面写。使用 node 进行调试。
先安装 redux。
npm install --save redux
实例代码如下:
const redux = require("redux");
//①创建一个纯函数reducers。
//这个函数接收两个参数:一个 state、一个 action,并返回新的 state。
const reducers = (state = {a : 10},action)=>{
if(action.type === "ADD"){
return {
a : state.a + 1
}
}else if(action.type === "UNADD"){
return {
a : state.a - 1
}
}
return state;
}
// ②创建store
const store = redux.createStore(reducers);
// ③通过getState()方法可访问state
console.log(store.getState().a);//10
// ④通过dispatch方法{type:"ADD"}进行一次加法运算
store.dispatch({type:"ADD"});
console.log(store.getState().a);//11
// ⑤通过dispatch方法{type:"UNADD"}进行一次减法运算
store.dispatch({type:"UNADD"});
console.log(store.getState().a);//10
- redux 依赖于一个纯函数(reducer),它描述了 action 如何将 state 转为新的 state,action 只说明了什么事情发生,但是不会描述 state 如何改变。绝对不能改变 state 对象,如果你要变,只能返回新的 state。什么是纯函数?不改变传入的参数的函数就是纯函数。
- redux 通过 createStore 让 store 和 reducers 产生联系。
- 产生联系的 store ,提供一个
getState()
方法来访问纯函数的 state,通过dispatch({"type":...})
来检测命令。唯一能够改变 state 的方法就是 dispatch 一个 action。store 就是统一管理 reducer 和 action 的,将所有的一切统一起来的。 - store 有三个主要的功能:
- 持有 app 的 state
- 允许通过 getState() 得到 state
- 允许通过 dispatch 来改变 state
注意的是:可以有多个纯函数,但只能有 1 个 store 。
- 我们知道 action 是 store 唯一的信息的来源且 action 需要被 dispatch() 函数派发,action 是纯的、扁平的 JavaScript 对象。
如果需要多个纯函数,则需要引入,combineReducers()
这个方法进行纯函数合并。
const redux = require("redux");
//①创建一个纯函数reducers。
const reducers = (state = {a : 10},action)=>{
if(action.type === "ADD"){
return {
a : state.a + 1
}
};
return state;
}
//⑥创建另一个纯函数reducers2。
const reducers2 = (state = {a : 100},action)=>{
if(action.type === "LOAD"){
return {
a : action.palyload
}
}
return state;
}
// ⑦多个需求只能拆分纯函数reducer,通过combineReducers合并成一个
const reducer = redux.combineReducers({
reducers,
reducers2
})
// ②创建store
const store = redux.createStore(reducer);
// ③通过getState()方法可访问state的加命名空间
console.log(store.getState().reducers.a);//10
// 但是通过dispatch却不用添加命名空间
store.dispatch({type:"ADD"});
console.log(store.getState().reducers.a);//11
//<============华丽的分割线===========>
// ③通过getState()方法可访问state的加命名空间
console.log(store.getState().reducers2.a);//100
// action可通过playload携带负载参数
store.dispatch({type:"LOAD",palyload:"我是携带的负载!"});
console.log(store.getState().reducers2.a);//我是携带的负载
纯函数里面的 action 就是由 type 属性的组成的 JSON,但实际上不仅仅有 type 属性,还可以有其他的属性,所有其他的属性都叫做载荷(payload)。
二、redux 结合 React
先安装:react-redux粘合剂官网:https://react-redux.js.org/
npm install --save react-redux
梳理一下 redux 和 react-redux 提供各自提供的 API。
- Redux 是可预测状态容器(reducer、state、store、dispatch、action、applyMiddleware 等API)
- react-redux 提供 Provider 和 connect 。
再看项目主要目录结构:
┣✈ main.js
┣✈ index.html
┣✈ webpack.config.js
┣✈ package.json
┗✈ views
┣✈ app
┣✈ App.js
┗✈ store
┣✈ index.js
┗✈ counterStore.js
展示项目目录代码:
- couterStore.js 放入纯函数:
export default (state = {a : 10},action)=>{
if(action.type === "ADD"){
return {
a : state.a + 1
}
}else if(action.type === "UNADD"){
return {
a : state.a - 1
}
};
return state;
}
使用 redux 第一步必须是写纯函数。
- index.js
import counterStore from "./counterStore.js";
import {combineReducers} from "redux";
export default combineReducers({
counterStore
});
使用 combineReducers
进行纯函数合并,变成一个统领文件
。
- main.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./views/app/App.js";
import {createStore} from "redux";
import {Provider} from "react-redux";
import reducers from "./views/store";
const store = createStore(reducers);
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>
,
document.getElementById("app")
);
引入 createStore 函数、引入r educers 统领文件、创建 store、可以测试使用store.getState().a
输出结果。Provider 可以让 store 照耀在 APP 里面所有的组件上,做到天下无人不识君。Provider 的机理是使用的 context 上下文机理。
- App.js
connect 这里是个高阶函数的用法,很明显 connect 进行了柯粒化处理。这里叫做装饰器。装饰器两个(mapStateToProps)(mapDispatchToProps)
,第一圆括号内部写如何装饰,映射全局 state 到组件的 props;第二个圆括号内部写装饰谁,将dispatch这个词语映射到 props 上。
写法一:不使用装饰器
import React,{Component} from "react";
import {connect} from "react-redux";
class App extends Component {
constructor(){
super()
}
render(){
return(
<div>
<h1>{this.props.a}</h1>
<button onClick = {this.props.add}>按我加一</button>
</div>
);
}
}
export default connect(
({counterStore})=>({
a : counterStore.a
}),
dispatch=>({
add(){
dispatch({type:"ADD"});
}
})
)(App);
写法二:装饰器写法
react 不支持装饰器写法,首先安装语法糖。
npm install --save @babel/plugin-proposal-decorators
参考 babel 官网 @babel/plugin-proposal-decorators · Babel
webpack.config.js 进行配置:
{
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }]
]
}
配置完,重新开启项目即可使用,改写代码:
import React,{Component} from "react";
import {connect} from "react-redux";
@connect(
({counterStore})=>({
a : counterStore.a
}),
dispatch=>({
dispatch
})
)
export default class App extends Component {
constructor(){
super()
}
render(){
return(
<div>
<h1>{this.props.a}</h1>
<button onClick = {()=>{this.props.dispatch({type:"ADD"})}}>按我加一</button>
</div>
);
}
}
两者实现的效果都是一样的。打开http://127.0.0.1:8080/
,查看效果:
三、bindActionCreators(基本不用)
Action Creator 就是一个创建 action 的函数,例如:() => ({“type” : “ADD”})
。不要混淆 action 和 action creator 这两个概念。Action 是一个信息的负载,而 action creator 是一个创建 action 的工厂。调用 action creator 只会生产 action,但不分发。bindActionCreators 这个 API 就是使用 Action Creator。
使用场景?所以是基本没用。
惟一会使用到
bindActionCreators
的场景是当你需要把 action creator 往下传到一个组件上,却不想让这个组件觉察到 Redux 的存在,而且不希望把dispatch
或 Redux store 传给它。
bindActionCreators(actionCreators, dispatch)
参数
actionCreators
(Function or Object): 一个 action creator,或者一个 value 是 action creator 的对象。
更改 App.js
import React,{Component} from "react";
import {connect} from "react-redux";
import * as actionCreators from "./actionCreators.js";
import {bindActionCreators} from "redux";
@connect(
({counterStore})=>({
a : counterStore.a
}),
(dispatch) => ({
actionCreators: bindActionCreators(actionCreators , dispatch)
})
)
export default class App extends Component {
constructor(){
super()
}
render(){
return(
<div>
<h1>{this.props.a}</h1>
<button onClick = {this.props.actionCreators.add}>按我加一</button>
</div>
);
}
}
新增的actionCreators文件
export const add = ()=>({type:"ADD"});
加法器仍然起作用。
四、安装 redux-logger
redux-logger 是每次 dispatch 的记录器。当我们启用 logger 的时候每次 dispatch ,都会在控制台输出每次详情。而我们只需要一装一引一配。
一装 redux-logger
npm install --save redux-logger
在主入口文件 main.js 中进行 一引一配
。
import React from "react";
import ReactDOM from "react-dom";
import App from "./views/app/App.js";
import {createStore,applyMiddleware} from "redux";
import {Provider} from "react-redux";
import reducers from "./views/store";
import logger from "redux-logger";
const store = createStore(reducers,applyMiddleware(logger));
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>
,
document.getElementById("app")
);
输入网址:http://127.0.0.1:8080/
查看控制台效果。
五、redux 的 hooks
hooks 在 v7.1.0 中首次被添加。这有一篇很好的文章,来介绍新出的 hook API 。
【译】React Redux API Hooks