最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 【Clean-State】新的React状态管理姿势

    正文概述 掘金(冷叶_)   2021-01-24   495

    click here !

    前言

    React从设计之初到最新的v17版本,已经经历了近百次迭代。围绕着单向数据流的设计哲学出现了以Flux思想为主的Redux状态管理和以响应式监听为主的Mobx,一个强调理念上的统一而另一个强调性能体验上的极致。但是通过唯物辩证法我们知道,对立和统一才是所有事物发展的最终形态。于是自React@v16.8.0后推出了Hooks函数,在不改变其心智模型的基础上补齐了对逻辑抽象的短板,借助这一能力我们就可以打开全新的状态管理视野。

    背景

    在目前以MVVM为核心的软件开发模式下,我们知道视图的本质就是对数据的表达,任何数据的突变都会带来视图上的反馈。当面临一个大型项目开发时,为了提高后续的维护迭代效率,我们首先要做的就是模块拆解,让每一个部分尽可能碎片化可复用,这也是微组件的初级概念。

    【Clean-State】新的React状态管理姿势

    而在整个拆解的过程中,我们碎片化的其实是UI层。比如一个弹窗,在特定的业务上有会统一的设计标准,变化的只是文案;亦或是一个大列表,每次更新的是元数据,卡片的轮廓保持了统一。那么数据该如何处理,试想如果跟随组件走,那当一个项目越来越大时,散落在各个地方的数据和逻辑会急剧增大该软件的熵,造成后面的需求迭代、错误排查、调试维护等难度指数级增大。所以,对数据一定程度上的中心化成为了前端正确的开发理念。

    方案

    在React里我们把与视图相对应的数据称之为状态,关乎状态管理的方案也经历了一个刀耕火种的时代。最出名的是Redux,它虽然在性能上被人诟病但是奈何思想正确被最大程度的使用。它将数据中心化为State存储在store中,通过dispatch来发布一个action触发reducer来更新。

    【Clean-State】新的React状态管理姿势

    设计理念是很好,但是当真正用到项目中时我们就会发现几个问题:

    1. 架构层面如何组织?这里我们不得不引入很多第三方开发库,比如react-redux、redux-thunk、redux-saga等等,这无疑增加了很大的学习成本,同时在寸土寸金的移动端会引入过大的包。
    2. 性能上如何避免无效渲染?我们通过react-redux做桥接后,关注过源码的同学会发现redux在react里更新的本质是变量提升,通过将state提升每次dispatch后都会触发顶层的setState。根据React的更新机制,这会触发所有子节点的Render函数执行。
    // Provider 注入
    import React from 'react'
    import ReactDOM from 'react-dom'
    
    import { Provider } from 'react-redux'
    import store from './store'
    import App from './App'
    
    const rootElement = document.getElementById('root')
    ReactDOM.render(
      <Provider store={store}>
        <App />
      </Provider>,
      rootElement
    )
    
    // connect 使用
    import { connect } from 'react-redux'
    import { increment, decrement, reset } from './actionCreators'
    
    // const Counter = ...
    const mapStateToProps = (state /*, ownProps*/) => {
      return {
        counter: state.counter,
      }
    }
    
    const mapDispatchToProps = { increment, decrement, reset }
    export default connect(mapStateToProps, mapDispatchToProps)(Counter)
    

    第二个方案是Mobx,它虽然能做到目标组件的精确更新,但是很快就被历史遗弃了。为什么呢?思想不正确,他的核心理念是:任何源自应用状态的东西都应该自动地获得。这句话就是说组件要不要更新不由父亲说了算,而是应该由绑定的数据通知,React在大谈单向数据流、可预测更新的时候你搞个响应式监听,在文革时期这就是反动。

    【Clean-State】新的React状态管理姿势

    当然这也只是其中的一个原因,还有几个比较重要的原因:其一是不够函数式,所有属性和方法都由Class声明,要知道react16以后就一直在推荐编写函数式组件,保留Class也是为了向下兼容;其二就是数据不够中心化,因为是基于响应式,所以这也不再重要。

    // 声明可观察状态
    import { decorate, observable } from "mobx";
    
    class TodoList {
        @observable todos = [];
        @computed get unfinishedTodoCount() {
            return this.todos.filter(todo => !todo.finished).length;
        }
    }
    
    // 声明观察组件
    import React, {Component} from 'react';
    import ReactDOM from 'react-dom';
    import {observer} from 'mobx-react';
    
    @observer
    class TodoListView extends Component {
        render() {
            return <div>
                <ul>
                    {this.props.todoList.todos.map(todo =>
                        <TodoView todo={todo} key={todo.id} />
                    )}
                </ul>
                Tasks left: {this.props.todoList.unfinishedTodoCount}
            </div>
        }
    }
    
    const TodoView = observer(({todo}) =>
        <li>
            <input
                type="checkbox"
                checked={todo.finished}
                onClick={() => todo.finished = !todo.finished}
            />{todo.title}
        </li>
    )
    
    const store = new TodoList();
    ReactDOM.render(<TodoListView todoList={store} />, document.getElementById('mount'));
    

    为什么是 Clean-State?

    那为什么我要开发clean-state呢?首先我们来看一下Hooks的设计动机是什么:

    1. 解决组件之间复用逻辑状态困难问题。
    2. 过多的生命周期导致组件难以理解。
    3. 消除class组件和函数组件分歧,简化模块定义。

    从这几点我们就能发现,hooks本质上就是要简化React学习使用的心智曲线,并在逻辑抽象方面再往前走一步。而Clean-State就是站在这一思想的肩膀上产生的,它告别了ReactContext的概念用极其精简的方法提出了状态管理的新方式。通过CS我们没有了更多的学习负担,也不需要人为的的组织架构,它提供了统一的解决方案,在性能上我们不再去做变量提升,也抛弃了Provider注入的方式因此可以做到模块级别的精确更新,下图罗列出来了他的一些特点。

    【Clean-State】新的React状态管理姿势

    在CS中,我们最大程度的尊崇极简主义原则,让开发用最简单的方式来构建产品大厦。

    模块如何划分

    在模块划分上,推荐以路由入口或者数据模型来区分,这符合自然的思维方式。

    每个状态管理的模块我们称之为module,统一管理在单个目录下,最后由index文件导出。

    |--modules
    |   |-- user.js
    |   |-- project.js
    |   |-- index.js
    

    模块如何定义

    在定义上,我们没有做更多的概念,沿袭了日常开发中最合理的方式。

    state 作为模块状态;effect处理副作用;reducer返回更新后的状态。

    // modules/user.js
    const state = {
      name: 'test'
    }
    
    const user = {
      state,
      reducers: {
        setName({payload, state}) {
          return {...state, ...payload}
        }
      },
      effects: {
        async fetchNameAndSet({dispatch}) {
          const name = await Promise.resolve('fetch_name')
          dispatch.user.setName({name})
        }
      }
    }
    
    export default user;
    

    模块如何注册

    你只需要在模块入口文件调用bootstrap即可,他会自动串联多个模块,并返回useModule和dispatch方法。

    // modules/index.js
    import user from './user'
    import bootstrap from 'clean-state'
    
    const modules = { user }
    export const {useModule, dispatch}  = bootstrap(modules);
    

    如何使用模块

    我们通过modules入口文件导出的useModule和dispatch来使用模块状态或者触发执行方法。

    // page.js
    import {useCallback} from 'react'
    import { useModule, dispatch } from './modules'
    
    function App() {
      /** 
       * 这里你也能够传入数组同时返回多个模块状态
       * const {user, project} = useModule(['user', 'project'])
       */
      const { user } = useModule('user')
      const onChange = useCallback((e)=> {
        const { target } = e
        dispatch.user.setName({name: target.value})
      }, [])
    
      const onClick = useCallback(()=> {
        dispatch.user.fetchNameAndSet()
      }, [])
    
      return (
        <div className="App">
          <div>
            <div>
              name: {user.name}
            </div>
            <div>
              修改用户名: <input onChange={onChange}></input>
            </div>
            <button onClick={onClick}>获取用户名</button>
          </div>
        </div>
      );
    }
    
    export default App; 
    

    如何跨模块访问

    每个reducer和effect我们都注入了rootState参数,可以访问其他模块属性;effect中同时注入了dispatch方法可以跨模块调用。

     async fetchNameAndSet({dispatch, rootState, state, payload}) {
          const name = await Promise.resolve('fetch_name')
          dispatch.user.setName({name})
     }
    

    混入机制

    在很多情况下,多个模块之间会存在公共的state、reducer或者effect,这里我们为了防止用户在每个模块里做重复声明,对外暴露了混入的方法。

    // common.js
    const common = {
      reducers: {
        setValue<State>({payload, state}: {payload: Record<string, any>, state: State}): State {
          return {...state, ...payload}
        }
      }
    }
    export default common;
    
    // modules/index.js
    import commont from './common'
    import user from './user'
    import { mixin } from 'clean-state';
    
    // Mix Common's setValue method into the User module
    const modules = mixin(common, { user })
    
    // You can now call the dispatch.user.setValue method on other pages
    export const {useModule, dispatch}  = bootstrap(modules);
    

    如何调试

    在开发过程中如何进行调试呢,CS提供了插件机制来友好的支持redux-devtool的调试。

    /**
     * 安装: npm install cs-redux-devtool
     */
    
    // modules/index.js
    import user from './user'
    import bootstrap from 'clean-state'
    import devTool from 'cs-redux-devtool'
    
    bootstrapfrom.addPlugin(devTool)
    
    ...
    

    经过以上简短的配置,我们就能通过Redux DevTool来追踪状态的变化了!

    最后

    Clean-State拥抱了React正确的设计模式和思想,通过精简的代码完成了架构层面的设计和视图层面的优化。

    如果你是新起的React项目,强烈推荐使用hooks纯函数的方式来编写构建你的应用,你会体验到更快的React开发姿势。无论是toB端逻辑复杂的大型项目还是toC端追求极致体验也都可以来了解使用CS。

    传送门: github.com/tnfe/clean-…

    【Clean-State】新的React状态管理姿势


    起源地下载网 » 【Clean-State】新的React状态管理姿势

    常见问题FAQ

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

    发表评论

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

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

    联系作者

    请选择支付方式

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