❤ 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
模塊吧
// code in models/login/state.js
export default ()=>({
firstName: "f",
lastName: "l",
});
複製代碼
// code in models/login/computed.js
export function nickName(n, o, f){
return `nick_${n.firstName}_${n.lastName}`
}
複製代碼
// 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>
</>
);
});
複製代碼
此組件裏firstName
和lastName
任意一個字段的值,改變都會引發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
,有如下幾點使用體驗提高
Ob
時傳入module
值便可獲取對應的數據Observe
必需配合useObserve
)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 ^_^
若是有關於concent的疑問,能夠掃碼加羣諮詢,會盡力答疑解惑,幫助你瞭解更多。