❤ 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的疑问,能够扫码加群咨询,会尽力答疑解惑,帮助你了解更多。