Redux系列- redux、react-redux

2,579 阅读7分钟

redux 基础概念

适用场景

  1. 复杂的用户交互
  2. 分权限、分不同的角色下
  3. 多用户数据共享、协作
  4. 与服务器交互多,使用了websocket
  5. 页面需要的数据来自多个渠道

基础概念

store

整个应用中只能有一个数据管理的容器对象store

state

包含当前时刻所有的数据.通过store.getState() 得到

action

用于发出通知,更改state . 通过store.dispatch() 发出Action

reducer

用于处理发出的Action通知. 需要返回一个全新的state

reducer 必须是纯函数. 同样的输入,必定得到同样的输出.

设计理念

  1. 单一数据源:易管理、易调试.
  2. state只读, 保证了数据更改只能通过一种方式修改Action
  3. reducer 纯函数处理数据更新

常用API释义

creatStore
import {createStore,combineReducers} from 'redux'
// reducer
function userReducer(state=INIT_STATE.userInfo,action) {
    switch(action.type){
        case 'update':
            return {
                ...state,
                ...action.payload
            }
        case 'delete':
            return action.payload
        default:
            return state
    }
}
// 创建
const store = createStore(reducer)
const {subscribe,dispatch,getState} = store
  1. createStore(reducer,[INIT_STATE],enhancer) 初始化创建 redux 容器 . 接受两个参数:reducer函数 、非必选INIT_STATEreducer函数中参数state 默认值、非必选enhancer 复合高阶函数更改store接口.
  2. reducer(state,action) 处理Action ,参数:当前的state、要处理的action
combindReducers

合并多个reducer , 参数为一个对象

import {createStore,combineReducers} from 'redux'
// 多个模块 的reducer 
import * as UserReducer from './reducers/User'
import * as LogReducer from './reducers/Log'

// 合并reducer 为一个整体
const reducer = combineReducers({UserReducer,LogReducer})
// 创建
const store = createStore(reducer)
数据容器对象 Store
  1. getState() 返回当前的 state

  2. dispacth(action) 分发 acion . 参数接受一个普通对象, 必须包含 type 字段表示类型 .

  3. subscribe(listener) 添加监听器 . state 发生变化时 , 调用 订阅函数listener

    返回值为一个取消订阅的函数 .

  4. replaceReducer(nextReducer) 替换当前用来计算stateredcuer .

中间件
  1. applyMiddleWare(...middleWares) 通过中间键扩展redux .
  2. 每个中间件接受两个参数:dispatchgetState . 返回一个函数next(action)用于手动调用下一个调用链(用于更改默认发起的aciton,不改变时默认自动调用) . 最后一个调用链会接收到实际真是的dispatch .
bindActionCreators(actionCreators,dispatch)

格式化将类如 store.dispatch(updateUser()) 包装后 可在组件中直接调用 updateUser() 调用的方式.

  1. 第一个参数为 actionCreator 或者值为actionCreator 的对象 .
  2. 第二个参数为 store 提供的 分发函数.

componse(...fun)

从右到左来组合多个函数.按顺序执行

redux 基础使用

安装

npm install redux --save

基础示例

分步使用:

  1. 创建 reducer ,
  2. 创建store.
  3. 订阅函数.
    1. 获取当前最新的 store
    2. 应用于视图UI.
    3. 数据变化更新视图.
  4. 事件分发.
import {createStore} from 'redux'

// 初始state
const INIT_STATE = {
    userInfo:{
        name:'admin',
        age:20,
    }
}
// reducer
function userReducer(state=INIT_STATE.userInfo,action) {
    switch(action.type){
        case 'update':
            return {
                ...state,
                ...action.payload
            }
        case 'delete':
            return action.payload
        default:
            return state
    }
}

// init
const store = createStore(userReducer)

// 订阅变化
// 变化时,打印数据
store.subscribe(()=>console.log(store.getState()))

// 分发action  
store.dispatch({type:'update',payload:{name:'test'}})
store.dispatch({type:'update',payload:{name:'test',age:32}})
store.dispatch({type:'delete'})
store.dispatch({type:'add'})

管理多个 reducer

根据业务量、复杂度 , 仅有一个reducer处理函数,会变得特别庞大 、难以维护 .

通过拆分模块、划分功能.创建多个reducer 进行单独模块的数据状态管理 .

userInfo.js 用于用户数据的管理,数据更新;

// userInfo.js
const INIT_STATE = {
    name:'admin',
    age:23,
    address:'江苏省 南京市'
}

export function UserInfo(state=INIT_STATE,action) {
    const {type,data} = action
    switch(type){
        case 'updateName':
            return {
                ...state,
                name:data,
            }
        case 'updateAge':
            return {
                ...state,
                age:data,
            }
        default:
            return state
    }
}

authInfo.js 用于当前登录用户的权限信息,权限更新;

// authInfo.js
const INIT_STATE = {
    role:'管理员',
    enable:false,
    operations:[],
}
export function AuthInfo(state=INIT_STATE,action) {
    
    const {type,data} = action
    switch(type){
        case 'updateRole':
            return {
                ...state,
                role:data,
            }
        case 'addOperate':
            // 拷贝 、 必要时深拷贝
            const obj = Object.assign(state)
            obj.operations.push(data)
            return obj
        default:
            return state
    }
}

reducers.js redux初始化主入口文件,汇总模块中的reducer;

// reducers.js
import {combineReducers} from 'redux'
// 导入模块reducer
import {UserInfo as user} from './UserInfo'
import {AuthInfo as auth} from './AuthInfo'

const reducers = combineReducers({
    user,
    auth,
})

export default reducers
// index.js
import {createStore} from 'redux'

import reducer from './reducers'
// init
const store = createStore(reducer)

// 订阅变化
// 变化时,打印数据
store.subscribe(()=>console.log(store.getState()))
store.dispatch({type:'INIT_STATE'})
store.dispatch({type:'updateName',data:'test'})
store.dispatch({type:'updateRole',data:'测试成员'})

React 中使用

在不使用react-redux下, 访问store

index.js 项目主入口文件;

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
// store
import store from './store'

const render = ()=>ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);
// store 变化重新执行渲染
render()
store.subscribe(render)

reduxBox/index.js 模拟一个功能模块,获取全局的store 数据。

// reduxBox/index.js
import { lazy } from "react";
import { useRouteMatch, Route, Redirect } from "react-router-dom";
// 直接导入store 数据
import store from '../store/index'
// 组件加载
const UserInfo = lazy(()=>import("./userInfo"))

export default function App(props) {
  const { url } = useRouteMatch();
  const {user,auth} = store.getState();
  return (
    <>
      <div>
        <p>store 数据</p>
        <p>姓名:{user.name} - 年龄:{user.age} - 地址:{user.address}</p>
        <p>角色名称:{auth.role} - 是否有效:{auth.enable} - 操作权限:{auth.operations}</p>
      </div>
      <Route exact path={url}>
        <Redirect to={`${url}/userInfo`} />
      </Route>
      <Route path={`${url}/userInfo`} component={UserInfo} />
    </>
  );
}

reduxBox/userInfo.js 用户信息模块,展示用户信息;可更新用户信息

// reduxBox/userInfo.js
import {Input} from 'antd'
import { useState } from 'react'
// 直接导入store 数据
import store from '../store/index'

export default function UserInfo(props) {
    const [name,setName] = useState('')
    // 获取数据
    const {user} = store.getState();

    return (<div style={{border:'1px solid #fff'}}>
        <p>当前状态</p>
        <p>姓名:{user.name} - 年龄:{user.age} - 地址:{user.address}</p>

        <Input onChange={e=>setName(e.target.value)} onPressEnter={e=>store.dispatch({type:'updateName',data:name})} />
    </div>)
}

通过Input 输入确定后可以看到组件本身、父组件的数据视图都已更新.似乎这样没有任何的问题;

它能正常工作的原因主要在于,store数据变化,整个项目重新渲染,拿到最新的数据。

// 初始化渲染
render()
// store 订阅更新, 数据发生变化时调用
// ReactDomo.render() 重复调用时, 进行更新操作,对有必要更改的DOM映射最新为最新的元素.
store.subscribe(render)

这种很明显的缺陷就是完全没有将store 数据融入到react中, 并不是react 一部分 , 在我们实际业务中,是什么引起的组件更新造成了很大的困扰.

  1. 不能使用shouldComponentUpdate 去控制 数据引起的组件更新.
  2. 不能使用 React.PureComponent 创建组件, 因为组件自身永远得不到要更新的理由.

解决的方式就是将store 数据流向需要的组件, 通过 props 或者context , 由于props局限(只能父传子), 所以react-redux 使用 context 来实现reduxreact 的数据连通, 将数据映射到组件的props

可以通过context 来让store 数据变化引起视图变化更像react 的风格

// store
import store from './store'
// 创建 context
export const StoreContext = React.createContext({})

const render = ()=>ReactDOM.render(
  <React.StrictMode>
    <StoreContext.Provider value={store.getState()}>
      <App />
    </StoreContext.Provider>
  </React.StrictMode>,
  document.getElementById('root')
);
// store 变化重新执行渲染
render()
// store.subscribe(render)

这是就不需要store.subscribe(render)去订阅更新了。

但是使用context还是不能让我们很好的控制组件的数据,虽然它有点像react风格了 , 所以有了react-redux

react-redux使用

安装

npm install --save react-redux

引起组件更新的数据更改:statepropscontext

API 释义

Provider

将redux store附加在整个应用层 , 类组件通过connect 获取数据和转发action, 函数组件通过 hooks useSelector\useDispatch

import {Provider} from 'react-redux'
// store
import store from './store'
// 创建 context
// export const StoreContext = React.createContext({})

const render = ()=>ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);
render()

在函数组件中使用hooks

useSelector

获取store 里的数据.

useDispatch

事件分发函数 .

代码示例说明:

// react-redux
import {useSelector,useDispatch} from 'react-redux'
export default function UserInfo(props) {
    const [name,setName] = useState('')
    // 获取数据
    const {user} = useSelector(state=>{
        const {user} = state
        return {
            user
        }
    })
    // 转发事件
    const dispatch = useDispatch()
    return (<div style={{border:'1px solid #fff'}}>
        <p>当前状态</p>
        <p>姓名:{user.name} - 年龄:{user.age} - 地址:{user.address}</p>

        <Input onChange={e=>setName(e.target.value)} onPressEnter={e=>dispatch({type:'updateName',data:name})} />
    </div>)
}

class 组件中使用

connect(mapStateToProps,mapDispatchToProps,mergeProps,options)

链接react组件与reduxstore

*  `mapStateToProps(gloablState,ownProps)` 链接`store` , 监听redux store变化,只要发生更改,就会更新此组件
*  `mapDispatchToProps(dispatch,ownProps)` 定义`action creator` , 通过调用,发起action
*  `mergeProps(stateProps,dispatchProps,ownProps)` 默认情况下返回`Object.assign({},wonProps,stateProps,dispatchProps)`
*  `options={pure:boolean,withRef:boolean}` `pure` 浅比较mergeProps的结果,避免不必要的更新;

bindActionCreators(fn|object:{fn}) 转换为 action creator ,减少样板的重复使用;

// 示例中
dispatch({type:'addOperate',data:values})
// 转换
const updateOperates = bindActionCreators((values)=>{
  return {
        type:'addOperate',
        data:values,
    }
},dispatch)
// 然后调用
updateOperates()

示例代码:

/**
 * 角色信息
 */
import React from 'react'
import {Select} from 'antd'
import {connect} from 'react-redux'
const {Option} = Select

class RoleInfo extends React.Component{
    render(){
        // 通过Props获取数据
        const {auth,dispatch} = this.props
        
        return(<div style={{border:'1px solid #fff'}}>
            <p>当前状态</p>
            <p>角色名称:{auth.role} - 是否有效:{auth.enable} - 操作权限:{auth.operations}</p>

            <Select style={{width:100}} mode="multiple" onChange={values=>dispatch({type:'addOperate',data:values})}>
                <Option value='1'>添加</Option>
                <Option value='2'>删除</Option>
                <Option value='3'>修改</Option>
            </Select>
        </div>)
    }
}

export default connect(
    state=>{
        const {auth} = state
        return {
            auth,
        }
    },
    dispatch=>{
        return {
            dispatch,
        }
    }
)(RoleInfo)

性能优化

  1. 阻止组件的重复渲染 .
  2. 衍生数据的重复计算 .

参考资料

redux 官网

redux 中文网

react-redux 官网

阮一峰 的redux 相关教程

redux 官方视频教程