Concent 2.4发布, 最小粒度观察与渲染组件单元

❤ star me if you like concent ^_^react

来自Observer的启发

在阅读mobx文档时,为了适配最新的函数组件,除了暴露一个api名为useObserver,还发现暴露了另一个比较有意思的组件Observer,支持更精细的控制渲染单元,大概用起来的姿式是这样的。git

在细说Observer以前,咱们先看看useObserver的使用套路,咱们先创建一个名为login的storegithub

import { observable, action, computed } from "mobx";

class LoginStore {
  @observable firstName = 'f'
  @observable lastName = 'l'

  @computed
  get nickName(){
    return `nick_${this.firstName}_${this.lastName}`
  }

  @action.bound
  changeFirstName(e) {
    this.firstName = e.target.value;
  }
  @action.bound
  changeLastName(e) {
    this.lastName = e.target.value;
  }
}

export default new LoginStore();
复制代码

紧接着实例化一个登陆框函数组件,使用useObserver切出一片须要观察和渲染的组件片断redux

import React from "react"
import { useObserver } from "mobx-react";
import store from "./models";

const LoginBox1 = () => {
  const { login } = store;
  return useObserver(() => (
    <div>
      <div>
        firstName:
        <input value={login.firstName} onChange={login.changeFirstName} />
      </div>
      <div>
        lastName:
        <input value={login.lastName} onChange={login.changeLastName} />
      </div>
      <div>
      nickName: {login.nickName}
      </div>
    </div>
  ));
};
复制代码

若是咱们须要进一步切割渲染范围,改变了哪一个属性的值就仅渲染与这个属性相关的视图片断,则可搭配Obserser来达到咱们的"切割"目的。api

import { useObserver, Observer } from "mobx-react";

const LoginBox2 = () => {
  const { login } = store;
  return useObserver(() => (
    <div>
      <Observer>
        {() => (
          <div>
            ob firstName:
            <input value={login.firstName} onChange={login.changeFirstName} />
          </div>
        )}
      </Observer>
      <Observer>
        {() => (
          <div>
            ob lastName:
            <input value={login.lastName} onChange={login.changeLastName} />
          </div>
        )}
      </Observer>
      <Observer>
        {() => <div>ob nickName: {login.nickName}</div>}
      </Observer>
    </div>
  ));
};
复制代码

注意此处的Observer组件使用方式,必需在useObserver回调内部使用,渲染它们看看效果吧。数组

查看在线示例bash

基于useConcent"切割"组件

咱们知道,concent已提供的接口useConcent,能够直接注册某个函数属于某个模块ide

function FooComp(){
    //属于login模块
    const { state } = useConcent('login');
    
    //当前视图对name有依赖
    return <div>{state.name}</div>
}
复制代码

也能够组成某个函数组件链接了其余多个模块函数

function FooComp(){
    //链接到了login模块,xxx模块
    const { connectedState } = useConcent({connect:['login', 'xxx']});
    
    //当前视图对login模块的name有依赖
    return <div>{connectedState.login.name}</div>
}
复制代码

固然了也能够既属于某个模块,同时也链接到其余模块,post

function FooComp(){
    //属于login模块, 链接到了xxx模块,yyy模块
    const { state, connectedState } = useConcent({module:'login', connect:['xxx', 'yyy']});
}
复制代码

因此只须要对useConcent作进一步的封装,便可达到支持观察与渲染最小粒度的组件单元的目的了,2.4版本里新暴露了组件Ob,就是其具体实现。

一样的咱们先来建立一个login模块吧

  • state
// code in models/login/state.js
export default ()=>({
  firstName: "f",
  lastName: "l",
});
复制代码
  • computed
// code in models/login/computed.js
export function nickName(n, o, f){
  return `nick_${n.firstName}_${n.lastName}`
}
复制代码
  • reducer
// code in models/login/reducer.js
export function changeFirstName(e) {
  return { firstName: e.target.value };
}

export function changeLastName(e) {
  return { lastName: e.target.value };
}
复制代码

写一个和useObserver目的同样的组件

import * as React from "react";
import { useConcent } from "concent";

const LoginBox1 = React.memo(() => {
  // mr is alias of moduleReducer
  const { state, moduleComputed: mcu, mr } = useConcent("login");

  return (
    <>
      <div>
        firstName:
        <input value={state.firstName} onChange={mr.changeFirstName} />
      </div>
      <div>
        lastName:
        <input value={state.lastName} onChange={mr.changeLastName} />
      </div>
      <div> nickName:{mcu.nickName}</div>
    </>
  );
});
复制代码

此组件里firstNamelastName任意一个字段的值,改变都会引发LoginBox1渲染,如今咱们像Observer组件同样细粒度的控制渲染范围吧

export const LoginBox2 = React.memo(() => {
  return (
    <>
     <h3>show Ob capability</h3>
      <Ob module="login">
        {([state, _, {mr}]) => (
          <div>
            firstName:
            <input value={state.firstName} onChange={mr.changeFirstName} />
          </div>
        )}
      </Ob>
      <Ob module="login">
        {([state, _, {mr}]) => (
          <div>
            firstName:
            <input value={state.lastName} onChange={mr.changeLastName} />
          </div>
        )}
      </Ob>
      <Ob module="login">
        {([_, computed]) => (
          <div> nickName:{computed.nickName}</div>
        )}
      </Ob>
    </>
  );
});
复制代码

渲染它们看看效果吧

查看在线示例

源码解读

useConcent返回的模块状态或者计算数据,自己具备运行时收集依赖的能力,因此咱们只需在源码里对useConcent作二次封装,就拥有了像Observer组件同样的提供更细粒度的观察与渲染组件的能力了。

import React from 'react';
import { useConcentForOb } from '../core/hook/use-concent';

const obView = () => 'Ob view';

export default React.memo(function (props) {
  const { module, connect, classKey, render, children } = props;
  if (module && connect) {
    throw new Error(`module, connect can not been supplied both`);
  } else if (!module && !connect) {
    throw new Error(`module or connect should been supplied`);
  }

  const view = render || children || obView;
  const register = module ? { module } : { connect };
  // 设置为1,最小化ctx够造过程,仅附加状态数据,衍生数据、和reducer相关函数
  register.lite = 1;
  const ctx = useConcentForOb(register, classKey);
  const { mr, cr, r} = ctx;

  let state, computed;
  if (module) {
    state = ctx.moduleState;
    computed = ctx.moduleComputed;
  } else {
    state = ctx.connectedState;
    computed = ctx.connectedComputed;
  }

  return view([state, computed, { mr, cr, r}]);
})
复制代码

在源码里,咱们对渲染函数提供状态数据,衍生数据、和reducer相关函数,方便用户按需选择,Ob组件对比Observe,有如下几点使用体验提高

  • 1,无需在代码实现处人工import对应的store,实例化Ob时传入module值便可获取对应的数据
  • 2,使用更自由,无需被嵌套在其余方法内(Observe必需配合useObserve
  • 3,保持和useConcent同样的使用方式,随处插拔,代码无需过多改造

动态的模块替换

useConcent容许动态的传入module或者connect参数,以此知足用户一些须要建立不一样模块组件的工厂函数场景。

首先咱们建立一个多模块的store吧,$$global模块用于存储选择的模块

run({
  counter: {
    state: { count: 999 }
  },
  counter2: {
    state: { count: 100000 }
  },
  $$global: {
    state: { mod: "counter" }
  }
});
复制代码

而后书写一个函数组件,由于须要动态的传入模块值,因此咱们须要先读取global模块的模块值,再传给useConcent以肯定属于哪一个模块。

const setup = ctx => {
  console.log(
    ctx.ccUniqueKey +
      " setup method will been called before first render period !!"
  );
  ctx.effect(()=>{
    return ()=>{
      console.log('trigger unmount ' + ctx.state.count);
    }
  }, [])

  return {
    add: () => ctx.setState({ count: ctx.state.count + 1 }),
  };
};

function SetupFnCounter() {
  const {state: { mod } } = useConcent(cst.MODULE_GLOBAL);
  const ctx = useConcent({ module: mod, setup });
  return (
    <View tag="fn comp with useConcent&setup --" add={ctx.settings.add} count={ctx.state.count} /> ); } 复制代码

注意这里,若是组件存在期切换了新的模块,当它们改变时,会有一次实力上下文卸载和重加载的过程,好比从counter切换为counter2,那么ctx.effect的返回函数做为unmount逻辑会被触发。

既然useConcent支持模块热替换,那么Ob固然也支持了,咱们书写一个切换模块的逻辑在顶层App里,同时也渲染一个Ob实例。

export default function App() {
  const {
    state: { mod },
    setState
  } = useConcent(cst.MODULE_GLOBAL);
  const changeMod = () =>
    setState({ mod: mod === "counter" ? "counter2" : "counter" });

  return (
    <div className="App"> <button onClick={changeMod}>change mod</button> <Ob module={mod}> {([state]) => { console.log("render ob"); return <div>xx: {state.count}</div>; }} </Ob> <SetupFnCounter /> </div>
  );
}
复制代码

最后看看效果吧^_^ 查看在线示例

往期回顾

❤ star me if you like concent ^_^

Edit on CodeSandbox

https://codesandbox.io/s/concent-guide-xvcej

Edit on StackBlitz

https://stackblitz.com/edit/cc-multi-ways-to-wirte-code

若是有关于concent的疑问,能够扫码加群咨询,会尽力答疑解惑,帮助你了解更多。

相关文章
相关标签/搜索