夜已经很深了,这个时间宣布2.0版本算是一个抢先预告吧,接下来的一个月里会重点开始更新文档以及铺开更多的示例了。
两个版本的差异
1.*
版本为了尽量向上兼容古老的浏览器,没有使用任何新的es特性,但是时代的洪流一定是向前走的,vue 3.0
也一定会今年登陆,所以终结 1.*
版本算是为了顺势而为,跟着潮流走,才能让开发者拥有最好的体验,2.*
启用defineProperty
重构了延迟计算特性,启用Proxy
重构了获取依赖的相关逻辑,具体区别见下面介绍
依赖标记(声明组件时) vs 依赖收集(实例渲染时)
在1.*
时代,组件的更新依赖是人工维护的,我们大致复盘一下
- 定义foo模块
import { register, useConcent } from 'concent';
// 定义foo模块
run({
foo: {
state: {
f1: "f1",
f2: "f1",
user: { name: "zzk", age: 19 }
}
}
});
-类组件写法,通过watchedKeys标识当前组件关心f1
,f2
,user
三个key的变化
@register({
module: "foo",
watchedKeys: ["f1", "f2", "user"]
})
class ClassComp extends Component {
state = { hidden: false };
render() {
console.log("ClassComp", this.ctx.renderCount);
const { state, sync, syncBool } = this.ctx;
// state === this.state
return (
<div style={{ border: "1px solid red", padding: "6px", margin: "6px" }}>
{!state.hidden ? <span>name: {state.user.name}</span> : ""}
<br />
{!state.hidden ? <span>age: {state.user.age}</span> : ""}
<br />
{!state.hidden ? (
<input value={state.user.name} onChange={sync("user.name")} />
) : (
""
)}
<br />
<button onClick={syncBool("hidden")}>toggle hidden</button>
</div>
);
}
}
- 函数组件写法,同样通过watchedKeys标识当前组件关心
f1
,f2
,user
三个key的变化
// 函数组件写法
const iState = () => ({ hidden: false });
function FnComp() {
const ctx = useConcent({
module: "foo",
state: iState,
// watchedKeys: ["f1", "f2", "user"]
});
console.log("FnComp ", ctx.renderCount);
const { state, sync, syncBool } = ctx;
return (
<div style={{ border: "1px solid red", padding: "6px", margin: "6px" }}>
{!state.hidden ? <span>name: {state.user.name}</span> : ""}
<br />
{!state.hidden ? <span>age: {state.user.age}</span> : ""}
<br />
{!state.hidden ? (
<input value={state.user.name} onChange={sync("user.name")} />
) : (
""
)}
<br />
<button onClick={syncBool("hidden")}>toggle hidden</button>
</div>
);
}
实例化这两个组件
import { setState } from 'concent';
export default function() {
return (
<>
<ClassComp />
<FnComp />
<button
onClick={() =>
// 此处通过concent的顶层api修改foo模块的user字段值
setState("foo", { user: { name: Date.now(), age: Math.random() } })
}
>call top api to change foo module state</button>
</>
);
}
如果在1.*
版本里,我们操作ClassComp组件实例或者FnComp组件实例的toggle hidden
按钮,其实视图里已经不再使用到user
这个属性了,但是我们我们如果外部任意地方修改了use
属性都会触发它们渲染。
在2.*
保留了1.*
版本的特性,同时修改了默认规则,1.*
版本不指定watchKeys
默认表示关心当前所属模块的所有stateKey
变化,2.*
版本则改为自动收集机制,即每一轮渲染动态的收集当前实例的关心key依赖,进一步提高性能。
所以属于某个模块的组件
funciton OneComp(){
const ctx = useConcent('foo');
const { f1, f2 } = ctx.state;//此轮渲染的依赖为f1, f2
}
或者连接了其他几个模块的组件
funciton OneComp(){
const ctx = useConcent({connect:['foo', 'bar']})
const { foo, bar } = ctx.connectedState;
const { f1, f2 } = foo;
const { b1 } = bar;
//此轮渲染的依赖为foo模块的f1, f2,bar模块的b1
}
类组件写法也一样
//定义此组件属于counter模块
@register('counter')
class OneComp extends React.Component{
add = ()=> this.setState({count:this.count+1})
render(){
//此轮渲染的依赖是count
return <h1>{this.state.count}</h1>
}
}
它们都将自动收集到相关的依赖,同时如果你强行修改状态的值,将会静默失败并给出警告,让你必需用setState
和dispatch
去修改(注sync
系列api是setState
的包装语法糖,不算违反规则)
注意新版React为了安全的启用异步渲染,在开发模式有
double-invoking
双调用检查机制(生产模式不会有此情况),所以示例里class有一次多余的渲染是正常现象。
延迟计算之方法获取 vs 属性获取
1.*
新增了惰性计算特性,即依赖某个stateKey的computed函数,在stateKey发生变化时,不触发计算,仅标记需要重新计算,待用户真正读取时才计算,并标记已计算同时缓存计算结果
同样我们先定义模块
import { register, run, useConcent, defLazyComputed } from "concent";
// run concent with a module named counter
run({
counter: {
state: { count: 12, msg: "--" },
reducer: {
inc(payload, moduleState, actionCtx) {
const curCount = payload !== undefined ? payload : moduleState.count;
return { count: curCount + 1 };
},
},
computed: {
// when count changed trigger will this fn execute
count(n, o, f) {
return n.count * 10;
},
// when count changed and read heavyCount will trigger this fn execute
heavyCount:defLazyComputed((n)=>{
return n.count * 1000000;
}, ['count'])
}
}
});
1.*
版本获取计算结果方式如下
// define a class component that belong to 'counter' module
@register("counter")
class Counter extends Component {
add = () => this.ctx.dispatch("inc");
render() {
const { moduleComputed } = this.ctx;
return (
<div>
count: {this.state.count}<br/>
heavy count: {moduleComputed.heavyCount()}<br/>
ten*count: {moduleComputed.count} <br />
<button onClick={this.add}>add</button>
</div>
);
}
}
// define a function component that belong to 'counter' module
function FnCounter() {
const ctx = useConcent("counter");
const add = () => ctx.dispatch("inc");
const {state, moduleComputed} = ctx;
return (
<div>
count: {state.count}<br/>
heavy count: {moduleComputed.heavyCount()}<br/>
ten*count: {moduleComputed.count} <br />
<button onClick={add}>add</button><br/>
msg: {ctx.state.msg}
</div>
);
}
可以看到非延迟计算的可以通过属性直接获取,而延迟计算属性则通过函数获取,所以对于1.*
的类型定义则是通过ComputedValType
获取时则是根据是否延迟而返回值类型or函数类型
- 而
2.*
基于defineProperty
重构后,则可以让用户直接直接通过属性获取计算结果了,更符合书写直觉习惯
// heavy count: {moduleComputed.heavyCount()}<br/>
改为
// heavy count: {moduleComputed.heavyCount}<br/>
同样的,类型提示页更加直接和简单,不再是一个函数
结语
concent
迭代升级的目标和react目标一样,为开发者提供极致的开发体验,为用户提供极致的使用体验,在新的版本里不考虑兼容古老浏览器后,新的es特性将让concent
如虎添翼,亲爱的读者,如果你喜欢,还请star和关注concent,抢先体验下2.*
版本.