1. 简介
redux 是 react 的状态管理 javaScript 库,存储公共数据,进行任意组件之间的通信。
2. 三大原则
单一数据源: 整个应用的 state 被存储在一颗 object tree 中,并且这个 object tree 只存在于唯一的一个 store 中。
state是只读的: 唯一改变 state 的方法就是 触发 action,action 是一个用于描述一发生事件的普通对象。
store.dispatc({
type:'DELETE_TODO',
index:1
})
使用纯函数来执行修改: 为了描述触发 action 后如何改变 store,你需要编写reducer。
3. redux的使用
3.1 安装 redux
npm i redux
3.2 创建 数据仓库
在项目目录下创建store文件夹,在store文件夹下创建数据仓库和纯函数。
import {createStore} from 'redux'
import reducer from './reducer';
// createStore(纯函数,应用插件) 创建一个唯一的store(数据仓库)
export default createStore(reducer);
3.3 创建纯函数
//在纯函数内部不能执行任何有副作用的代码 (发请求,定时器,事件监听,new Date() )
//定义state初始状态:
var data = {
count:100,
list:[],
loading:false
}
/**
* 定义纯函数
* @param {} 初始状态
* @param {} 触发的action
* @returns {} 新的state
*/
function reducer(state=data , action={type:'',payload:null}){
var newState = JSON.parse(JSON.stringify(state));
// 判断action 通过action类型,执行不同的操作来修改state
switch(action.type){
case 'ADD':
newState.count += action.payload;
console.log(newState);
//....
return newState;
case 'MIN':
//...
return newState;
case 'EDIT':
//...
return newState;
default:
return state;
}
// 必须返回新的state (如果触发了action就一定修改了state,如果没有触发action就一定没有修改state)
}
export default reducer;
3.4 如何使用store
import React, { Component } from 'react'
// 导入数据仓库
import store from '../store'
export default class Index extends Component {
componentDidMount() {
//设置监听store数据变化之后重新渲染组件,不然页面不会更新
store.subscribe(() => {
this.setState({})
})
}
add = () => {
// 通过触发action来修改store中的数据
store.dispatch({ type: 'ADD', payload: 1 })
console.log(store.getState())
}
render() {
return (
<div>
<h1>Index</h1>
{/* 展示store的数据 */}
<div>{store.getState().count}</div>
<button onClick={this.add}>加一</button>
</div>
)
}
}
3.5 store的三个核心api(在组件内使用)
store.dispatch(): 触发action,修改store中的数据。
store.dispatch({type:'',payload:null})
store.getState(): 获取store中的数据。
store.getState()
store.subscribe(): 监听store中的数据变化,调用setState更新组件。通过调用subsctibe返回的函数,取消监听。
store.subscribe(()=>{
this.setState()
})
4.redux和组件的关系
5. redux数据流
组件中通过dispatch触发一个action,通过纯函数修改state,最后将新的state保存到store中。(保持一个严格的单向数据流)
6. 中间件
6.1 日志打印 redux-logger
- 安装
npm i redux-logger
- 使用
import {createStore,applyMiddleware} from 'redux'
import reducer from './reducer';
import logger from 'redux-logger'
/**
应用插件,通过调用redux的applyMiddleware函数将插件当作参数传递进去。
*/
// createStore(纯函数,应用插件) 创建一个唯一的store(数据仓库)
export default createStore(reducer,applyMiddleware(logger));
6.2 持久化
- 安装
npm i redux-persist
-
使用
第一步:先从redux-persist中导入persistStore和persistReducer方法。
第二步:从redux-persist/lib/storage中导出一个storage //数据持久化存储在哪里(默认在localstorage中)。
第三步:调用persistReducer方法,生成一个持久化的reducer {key:'redux',storage} 存储在storage中的名字 var persistedReducer = persistReducer( {key:'redux',storage}, reducer )
创建store var store = createStore(persistedReducer,applyMiddleware(logger));
生成持久化的store export var persistor = persistStore(store) //调用persistStore方法,生成一个持久化的store
import {createStore,applyMiddleware} from 'redux'
import reducer from './reducer';
import logger from 'redux-logger'
import {persistStore,persistReducer} from 'redux-persist' //在redux-persist中导出persistStore和persistReducer
import storage from 'redux-persist/lib/storage' //数据持久化存储在哪里(默认在localstorage中)
// createStore(纯函数,应用插件) 创建一个唯一的store(数据仓库)
//调用persistReducer方法,生成一个持久化的reducer {key:'redux',storage} 存储在storage中的名字
var persistedReducer = persistReducer(
{key:'redux',storage},
reducer
)
// 创建store
var store = createStore(persistedReducer,applyMiddleware(logger));
//生成持久化的store
export var persistor = persistStore(store) //调用persistStore方法,生成一个持久化的store
// 抛出
export default store;
第四步:去到根目录下,导入import {PersistGate} from 'redux-persist/integration/react' 组件,然后用它包裹根组件。然后给PersistGate添加loading和persistor
通过 createContext创建一个context对象,使用context中的Provider组件包裹,然后将store传入
import React,{createContext} from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {BrowserRouter,HashRouter} from 'react-router-dom'
import {PersistGate} from 'redux-persist/integration/react'
import store, {persistor} from './store/index'
var context = createContext()
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
// <React.StrictMode>
<BrowserRouter>
{/* Provider 负责将store传入到内层所有组件中 */}
<context.Provider store ={store} >
{/* PersistGate 负责在内部每个组件中获取store中数据的时候,会自动从本地存储中获取数据,并更新到store中。如果那个组件中修改了数据,会自动将数据更新到本地存储中 */}
<PersistGate loading={null} persistor={persistor} >
<App />
</PersistGate>
</context.Provider>
</BrowserRouter>
// </React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
7. 避免store监听的内存泄露
当设置subscribe函数监听store变化的时候,组件如果多次挂载,卸载,就会产生多个监听,会造成内存泄露。
解决方法: 可以在subscribe函数参数的回调函数里面return出一个函数,然后再组件的 componentDidMount 方法里面调用subsctibe函数返回的方法取消监听。
import React, { Component } from 'react'
// 导入数据仓库
import store from '../store'
export default class Index extends Component {
componentDidMount() {
//设置监听store数据变化之后重新渲染组件,不然页面不会更新
this.unsubcribe = store.subscribe(() => {
this.setState({})
return () => { }
})
}
componentWillUnmount() {
this.unsubcribe(); //通过回调函数取消监听
}
add = () => {
// 通过触发action来修改store中的数据
store.dispatch({ type: 'ADD', payload: 1 })
console.log(store.getState())
}
render() {
return (
<div>
<h1>Index</h1>
{/* 展示store的数据 */}
<div>{store.getState().count}</div>
<button onClick={this.add}>加一</button>
</div>
)
}
}
8. redux 工程化开发
8.1 reducer 的拆分跟合并
如果需要管理的数据多的话,就会造成不好维护的问题,所有需要拆分reducer,将数据通过指定的子reducer去维护。
拆分
在store文件夹下新建reducers文件夹,存储拆出去的子reducer。
function arrReducer(state = [],action = {type:'',payload:null}){
// 深拷贝一份state
var newState = JSON.parse(JSON.stringify(state));
// 判断action 通过action类型,执行不同的操作来修改state
switch(action.type){
case 'ADD_ARR':
newState.push(action.payload);
//....
return newState;
case 'MIN_ARR':
//...
return newState;
case 'EDIT_ARR':
//...
return newState;
default:
return state;
}
}
export default arrReducer;
function countReducer(state = 200,action = {type:'',payload:null}){
// 深拷贝一份state
var newState = JSON.parse(JSON.stringify(state));
// 判断action 通过action类型,执行不同的操作来修改state
switch(action.type){
case 'ADD':
newState += action.payload;
//....
return newState;
case 'MIN':
//...
return newState;
case 'EDIT':
//...
return newState;
default:
return state;
}
}
export default countReducer;
合并
导入redux提供的combineReducers方法。第一个参数是所有子reducer,可以为他设置命名空间。
//合并子reducer
import {combineReducers} from 'redux'
//导入子reducer
import arrReducer from './reducers/arrReducer'
import countReducer from './reducers/countReducer'
// 合并2个子reducer为一个总的reducer
export default combineReducers({
count:countReducer,
list:arrReducer
})
8.2 动态生成action
封装一个函数,用来动态的生成action,action参数通过函数的参数传递进来。在组件中可以调用这个函数去生成新的action。
// 动态生成action
function create_add_action(payload){
return {type:'ADD',payload}
}
function create_SUB_action(payload){
return {type:'SUB',payload}
}
8.3 redux-thunk中间件及异步
redux需要的action是一个纯文本的对象,但是我们要做异步操作的时候,函数动态生成的是一个函数类型的action,需要用到一个中间件,redux-thunk,来处理函数类型的中间件。 也可以理解成redux-thunk中间件帮我们调用了函数action。
安装
npm i redux-thunk
使用
import {createStore,applyMiddleware} from 'redux'
import reducer from './reducer';
import logger from 'redux-logger'
import {thunk} from 'redux-thunk'
import {persistStore,persistReducer} from 'redux-persist' //在redux-persist中导出persistStore和persistReducer
import storage from 'redux-persist/lib/storage' //数据持久化存储在哪里(默认在localstorage中)
// createStore(纯函数,应用插件) 创建一个唯一的store(数据仓库)
//调用persistReducer方法,生成一个持久化的reducer {key:'redux',storage} 存储在storage中的名字
var persistedReducer = persistReducer(
{key:'redux',storage},
reducer
)
// 创建store
var store = createStore(persistedReducer,applyMiddleware(thunk,logger));
//生成持久化的store
export var persistor = persistStore(store) //调用persistStore方法,生成一个持久化的store
// 抛出
export default store;
组件中
// 做一些异步操作
/**
* 在这里调用函数,其实返回的是一个函数,但是redux需要的是一个纯文本的对象,需要用到一个中间件,redux-thunk,来处理函数类型的中间件
*/
store.dispatch(create_request_action(1))
动态生成action的函数
// 需要去做一些异步操作的时候,需要使用redux-thunk中间件
export function create_request_action(payload){
return function(dispatch){
// 在这里做异步操作
setTimeout(() => { //这个函数的参数是store.dispatch()
// 触发action
dispatch(create_add_action(1))
}, 1000);
}
}
8.4 react-redux
是一个官方推荐的react跟redux绑定的库。使用这个库之后,在组件内部就不需要通过store api去操作仓库了,也不需要去监听。降低了代码的复杂度。
安装
npm i react-redux
使用
使用react-redux 提供的一个Provider组件包裹,确保所有组件都能拿到store。
import React,{createContext} from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {BrowserRouter,HashRouter} from 'react-router-dom'
import {PersistGate} from 'redux-persist/integration/react'
import store, {persistor} from './store/index'
import { Provider } from 'react-redux';
var context = createContext()
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
// <React.StrictMode>
<BrowserRouter>
{/* Provider 负责将store传入到内层所有组件中 */}
<Provider store={store}>
{/* <context.Provider store ={store} value=''> */}
{/* PersistGate 负责在内部每个组件中获取store中数据的时候,会自动从本地存储中获取数据,并更新到store中。如果那个组件中修改了数据,会自动将数据更新到本地存储中 */}
<PersistGate loading={null} persistor={persistor} >
<App />
</PersistGate>
{/* </context.Provider> */}
</Provider>
</BrowserRouter>
// </React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
内层组件如何取到store:
在react-redux中导出一个connect函数,他是一个高阶组件,它的前两个参数是函数。它的返回值是一个函数,调用返回的函数将组件传入进去返回一个新的组件。
作用:将store中的数据注入到组件的props中。
connect函数参数的作用:第一个参数,将store中的数据注入到组件的props中。第二个参数函数接收一个dipatch函数用来触发action,然后将将方法注入到props中。
//使用react-redux来管理数据
import React, { Component } from 'react'
// 导入connect方法,将组件和store绑定起来
import { connect } from 'react-redux'
import { create_add_action, create_request_action } from '../store/actionCtraters/index'
class Index extends Component {
add = () => {
// 通过触发action来修改store中的数据
// this.props.add();
this.props.request();
}
render() {
return (
<div>
<h1>Index</h1>
{/* 展示store的数据 */}
<div>{this.props.count}</div>
<button onClick={this.add}>加一</button>
</div>
)
}
}
// connect函数前两个参数是函授,他的返回值还是一个函数,需要将组件传进去
// 作用:注入store中的数据到组件的props中
var NewIndex = connect(
(state) => { // 将store中的数据注入到组件的props中
// state store中的数据
return {
count: state.count // 将store中的count注入到组件的props中
}
},
(dispatch) => { //绑定方法到组件的props中
// dispathc store中的dispatch方法
return {
add: () => {
// 调用同步action
dispatch(create_add_action(1))//触发action
},
request: () => {
// 调用异步action
dispatch(create_request_action())
}
}
}
)(Index)
export default NewIndex;
9. redux的中间件机制
redux中间件是在reducer之前执行的,他其实是一个函数。
自己实现一个中间件
export const middleware = function({dispatch,getState}){
return function(next){
return function(action){
console.log('自己写的middleware')
return next(action) //将控制权移交给下一个中间件
}
}
}
如何使用 跟使用其他中间件一样
import {createStore,applyMiddleware} from 'redux'
import reducer from './reducer';
import logger from 'redux-logger'
import {thunk} from 'redux-thunk'
// 导入自己实现的中间件
import {middleware} from './middlewares'
import {persistStore,persistReducer} from 'redux-persist' //在redux-persist中导出persistStore和persistReducer
import storage from 'redux-persist/lib/storage' //数据持久化存储在哪里(默认在localstorage中)
// createStore(纯函数,应用插件) 创建一个唯一的store(数据仓库)
//调用persistReducer方法,生成一个持久化的reducer {key:'redux',storage} 存储在storage中的名字
var persistedReducer = persistReducer(
{key:'redux',storage},
reducer
)
// 创建store
var store = createStore(persistedReducer,applyMiddleware(thunk,logger,middleware));
//生成持久化的store
export var persistor = persistStore(store) //调用persistStore方法,生成一个持久化的store
// 抛出
export default store;
实现一个日志打印中间件
export const myLooger = function({dispatch,getState}){
return function(next){
return function(action){
console.group(action.type); // 开始一个新的日志组
console.info('dispatching', action); // 打印即将被dispatch的action
let result = next(action); // 调用下一个中间件或reducer
console.log('next state', getState()); // 打印新的state
console.groupEnd(); // 结束日志组
return result;
}
}
}