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的疑問,能夠掃碼加羣諮詢,會盡力答疑解惑,幫助你瞭解更多。

相關文章
相關標籤/搜索