五、React-redux

244 阅读10分钟

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和组件的关系

image.png

image.png

5. redux数据流

组件中通过dispatch触发一个action,通过纯函数修改state,最后将新的state保存到store中。(保持一个严格的单向数据流)

6. 中间件

6.1 日志打印 redux-logger

  1. 安装
npm i redux-logger
  1. 使用
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 持久化

  1. 安装
npm i redux-persist
  1. 使用

    第一步:先从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;
        }

    }
}