一、redux
1、安装 yarn add redux
2、工作原理图解析
流程步骤:
React Components(客人): react组件想要做什么事情,比如做加法运算
Action Creators(客人选择做菜的方式和选什么菜): 将菜单写好发送给饭店老板(store),action有两个参数 store.dispatch({ type: ‘用哪种方式做’, data: ‘做什么菜’ })
import store from '../../redux/store';
import { createIncrementAction, createDelcrementAction } from '../../redux/count_action';
inCrement = () => {
const { value } = this.selectState; // 这是要传入计算的值
// createIncrementAction这个是从count_action.js中封装好的action方法
store.dispatch(createIncrementAction(value));
};
Store(饭店老板): 将菜单发给厨师,同时负责将厨师做好的菜送到客人面前
创建store.js文件
import { createStore } from 'redux'; // 从redux中取出创建仓库函数
import Reducer from './count_reducers'; // 引入加工的reducer
export default createStore(Reducer); // 创建实例同时传入reducer并暴露加工后的reducer值
Reducers(厨师):将菜单上的做菜方式和客人选的菜做好后交给老板,他有两个参数 preState,action
preState:上一个值,也可以直接在形参中初始值
action:接收到客人的action对象,其中有type和data
创建count_reducer.js文件
import { INCREMENT, DELCREMENT } from './constants';
export default function Reducer(initPreState = 0, action) {
const { type, data } = action;
switch (type) {
case INCREMENT:
return initPreState + data * 1;
case DELCREMENT:
return initPreState - data * 1;
default:
return initPreState;
}
}
最终React Components (react组件)希望拿到reducers计算后的值就需要从Store身上获取
const count = store.getState() // 取值
store.getState() // 页面显示
页面更新(订阅):subscribe(注意别写成subscript)
两种方式
1、在使用的组件中用ComponentDidMount生命周期中更新
store.subscribe(()=>{
this.setState({})
})
2、一劳永逸,直接在index.js react入口文件的渲染root根标签外部用store.subscribe包裹,好处是整个root内部的所有文件的store都有实时数据更新的效果
store.subscribe(() => {
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
});
3、优化:
1、action常量集
创建constants.js
作用:一般用于reducers中使用,如果需要更改方法直接在constants.js常量集 中修改type类型,
constants.js
export const INCREMENT = 'inCrement'
例如现在在reducers.js中
import { INCREMENT, DELCREMENT } from './constants';
export default function Reducer(initPreState = 0, action) {
const { type, data } = action;
switch (type) {
case INCREMENT:
return initPreState + data * 1;
case DELCREMENT:
return initPreState - data * 1;
default:
return initPreState;
}
}
2、封装创建action方法常量集
创建count_action.js 作用:封装一个action对象
import { INCREMENT } from './constants';
export const createIncrementAction = data => ({ type: INCREMENT, data })
使用的页面需要引入封装好的action方法
import { createIncrementAction } from '../../redux/count_action';
示例:
store.dispatch({ type: 'delCrement', data: value }); // 这是没封装前的传参
store.dispatch(createDelcrementAction(value)); // 使用封装的action
4、传递异步action
action有两种方式传参
第一种:传对象 { type: 'inCrement', data } 同步的
第二种: 传函数 异步的 延迟操作或者接口不喜欢在组件内调用就可以统一写在封装count_action.js集里面
// 引入redux自带的异步中间件 yarn add redux-thunk
// 为啥要下载这个插件? 因为redux异步需要中间件的支持才能传递action,所以这是必经之路。
// 在store.js中导入取出,同时从redux中取出应用中间件函数 applyMiddleware
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
// 然后在暴露仓库的参数中应用一下,将第三方插件引入,这样整个store仓库就可以接收和返回异步action了
export default createStore(Reducer, applyMiddleware(thunk));
count_action.js
export const createIncrementAction = data => ({ type: INCREMENT, data });
export const createIncrementAsyncAction = (data, delay) => {
return dispatch => {
setTimeout(() => {
// 由于我们需要的方法其他action已经有了,所以可以直接使用,只需要将我们需要传的参数传入即可
// 分发有两种写法
// 一种是顶部引入store.js文件
// store.dispatch(createIncrementAction(data))
// 第二种是直接在当前return 这个函数体中本身就是在返回store对象,所以可以从形参dispatch直接 传,这样就少写store和少导入store.js文件。
dispatch(createIncrementAction(data));
}, delay);
};
};
组件中使用
导入
import { createIncrementAsyncAction } from '../../redux/count_action';
asyncCrement = () => {
const { value } = this.selectState;
store.dispatch(createIncrementAsyncAction(value, 500));
};
二、React-redux (facebook)
1、安装 yarn add react-redux
2、工作原理图解析
容器组件:主要存放容器UI,它自身拥有操作store中的状态和数据的方法api
容器UI:通过this.props获取到容器组件传递过来的store数据进行页面渲染
创建 containers 文件夹并在下面创建 Count 文件 -> index.js (存放容器组件的)
containers -> Count -> index.js
// 引入容器UI
import CountUI from '../../components/Count';
// 取出connect 容器UI 和 容器组件的连接器
import { connect } from 'react-redux';
// 引入封装好的action方法
import {
createIncrementAction,
createDelcrementAction,
createIncrementAsyncAction,
} from '../../redux/count_action';
// 修改store数据 形参是store中的reducer处理后返回给它的数据赋值给count属性,再将count属性传递给子组件,子组件Count就可以通过this.props.count获取到store中的数据了
const mapStateToProps = state => {
return { count: state };
};
// 分发action到store去修改状态
const mapDispatchToProps = dispatch => {
return {
jia: number => dispatch(createIncrementAction(number)),
jian: number => dispatch(createDelcrementAction(number)),
jiaAsync: (number, delay) => dispatch(createIncrementAsyncAction(number, delay)),
};
};
// 连接器需要通过调用两个函数并传入相关参数,
// 第一个函数有两个参数
// 两个参数都是得传{}形式,一个映射取数据,另一个是映射发送action到store让reducer去修改状态
// 第二个函数是传入一个容器UI从而生成一个容器组件
// 整句代码的意思是传入要加工的action和导入组件并创建暴露出容器组件
export default connect(mapStateToProps, mapDispatchToProps)(CountUI);
在App.jsx 入口文件引入 Count 容器组件用于渲染,为了让store的数据给Count 容器组件内的容器UI都能使用,需要引入并在组件中传递
App.jsx
import React, { Component } from 'react';
import Count from './containers/Count'; // 容器组件
import store from './redux/store'; // 1
export default class App extends Component {
render() {
return (
<div>
<Count store={store} /> // 1
</div>
);
}
}
component(存放容器UI)
component -> count -> index.jsx 当前容器UI
inCrement = () => {
const { value } = this.selectState;
// 通过this.props取值
this.props.jia(value);
};
3、优化
1、容器组件代码 connect第一个回调传递有两种方式
/* 第一种 用函数变量形式传入
修改store数据 形参是store中的reducer处理后返回给它的数据赋值给count属性,再将count属性传递给子组件,子组件Count就可以通过this.props.count获取到store中的数据了
*/
const mapStateToProps = state => {
return { count: state };
};
// 分发action到store去修改状态
const mapDispatchToProps = dispatch => {
return {
jia: number => dispatch(createIncrementAction(number)),
jian: number => dispatch(createDelcrementAction(number)),
jiaAsync: (number, delay) => dispatch(createIncrementAsyncAction(number, delay)),
};
};
// 创建一个容器组件并链接容器UI 传入两个映射函数变量
export default connect(mapStateToProps, mapDispatchToProps)(CountUI);
/* 第二种 利用react-redux自带的dispatch以及语法优化让代码更简洁,当然也需要看得懂才行
创建一个容器组件并链接容器UI
第一个参数 直接将state表达式写入connect()
第二个参数 用对象key: value的形式传入,直接调用封装好的action函数就可以,这个参数对象react-redux会自动加上dispatch,所以不用再手动写dispatch */
export default connect(
state => ({ count: state }),
{jia: createIncrementAction,
jian: createDelcrementAction,
jiaAsync: createIncrementAsyncAction})
(CountUI);
2、 引入Provider并在标签中传入store,这样App下的所有组件都可以使用到store的状态和数据了
优化前:
redux还需要在index.js中使用store.subscribe让所有App下的应用使用store都能实时更新,同时哪些容器组件,例如Count中要给自身的容器UI传store还要在容器组件Count中进行引入和传值
缺点:如果多个容器组件那么需要给每个容器组件传store,见下面的ForExample1、ForExample2
App.jsx
import Count from './containers/Count';
import store from './redux/store';
export default class App extends Component {
render() {
return (
<div>
<Count store={ store } />
<ForExample1 store={ store } />
<ForExample2 store={ store } />
</div>
);
}
}
优化后:
项目根目录入口文件 index.js
import store from './redux/store';
import { Provider } from 'react-redux';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);
3、容器组件和容器UI整合版
优化目的:
原本每生成一个容器组件就需要创建容器组件和容器UI文件,1 -> 2,量多文件就翻倍增长
容器UI:components -> Count -> index.jsx
容器组件:contaners -> Count -> index.jsx
优化后代码:
constaners -> Count -> index.jsx
/*
四个步骤:
1、创建容器UI
2、导入react-redux连接器 connect
3、连接容器UI创建并生成容器组件暴露出去
4、连接时传入映射状态和映射操作方法
*/
import React, { Component } from "react";
import { connect } from "react-redux"; // 2
import { createIncrementAction } from "../../redux/count_action";
// 1
class Count extends Component {
increment = () => {
this.props.incrementFN(1);
};
render() {
return (
<div>
<h2>当前计算后的值 {this.props.sumCount} </h2>
<button onClick={this.increment}>加1</button>
</div>
);
}
}
// 3 4
export default connect((state) => ({ sumCount: state }), {
incrementFN: createIncrementAction,
})(Count);
为了让每个新增的对象都拥有唯一的ID,所以可以使用第三方插件nanoid,当然也可以用时间戳或者其他方式
安装命令 yarn add nanoid
如何使用
import { nanoid } from 'nanoid'
直接调用即可 <div> { nanoid() } </div>
combindReducer:可以用对象形式合并多个reducer为一个总reducer,统一发给store进行操作,让redux不再只为一个reducer服务
从redux引入 用对象key:value的形式传入,这样store就可以保存所有状态了
store.js
import { createStore, applyMiddleware, combineReducers } from 'redux'
import countReducer from '../redux/reducers/count';
import personReducer from '../redux/reducers/person';
export default createStore(
combineReducers({
count: countReducer,
person: personReducer
})
, applyMiddleware(thunk));
页面获取store中的状态、
以Person容器组件为例
connect连接器保存着组件的状态,state就是当前容器组件保存在store中的数据,当前容器组件还可以获取其他容器组件的状态。这一过程实现了状态数据共享。
export default connect(
(state) => ({ renArr: state.person, countSum: state.count }),
{
jiayiren: createPersonAction,
}
)(Person);
4、react redux 开发工具的使用
store.js
/* 下载命令 yarn add redux-devtools-extension
引入 import { composeWithDevTools } from 'redux-devtools-extension'
将 composeWithDevTools 整个react-redux开发工具包裹整个异步action
composeWithDevTools(applyMiddleware(thunk)) */
export default createStore(
combineReducers({
he: countReducer,
rens: personReducer
})
, composeWithDevTools(applyMiddleware(thunk)));
5、将所有容器组件的reducer汇总到一个js文件中,store使用reducer就直接引入这个js文件进行操作
优点:这样如果需要修改reducer或者新增reducer就只需在reducer -> index.js中进行操作,同时store.js中也无需引入大量的reducer,代码更清晰、后期更好维护、更人性化。
reducer -> index.js
在reducer下创建index.js,在该文件中引入所有容器组件的reducer,最终统一暴露一个汇总后的reducer对象
import count from './count';
import person from './person';
// 引入整合reducer方法
import { combineReducers } from 'redux';
export default combineReducers({count, person})
store.js
// 引入统一汇总后的reducer
import reducer from './reducers'
export default createStore(reducer, composeWithDevTools(applyMiddleware(thunk)));