最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Redux系列- redux、react-redux

    正文概述 掘金(hboot)   2021-01-24   517

    redux 基础概念

    适用场景

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

    基础概念

    store

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

    state

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

    action

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

    reducer

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

    设计理念

    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 官方视频教程


    起源地下载网 » Redux系列- redux、react-redux

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元