run载入模块配置,register注册类组件,useConcent注册函数组件。
启用Proxy在运行时动态收集每一个组件的最新依赖列表,保证最小粒度更新,同时内置renderKey、lazyDispatch、delayBroadcast等高级特性满足更复杂的更新场景。
支持组件跨多个模块消费数据,同时所有组件都自动连接到内置的$$global模块。
无根Provider包裹,注册后的组件setState即可更新store。
模块提供state、reducer、watch、computed和init 5个选项,支持按需定义,覆盖所有业务场景。
除setState之外,还支持dispatch、invoke提交数据变更,同时让ui视图与业务逻辑彻底解耦。
支持实例级别computed、watch定义;支持组件emit&on;支持setup特性实现composition api.
hoc、render props和hook 3种方式定义的组件均享有一致的api调用体验,相互切换代价为0。
对于class组件,默认采用反向继承策略,让react dom树的层级结构保持简洁与干净。
支持定义中间件拦截所有的数据变更提交记录做额外处理,也支持自定义插件接收运行时的各种信号,增强concent能力。
支持任意地方调用configure接口动态配置模块,方便就近配置模块,且能够独立打包组件发布npm。
支持对已定义模块进行克隆运行时是完全独立的新模块,满足抽象工厂函数等高维度抽象。
npm install concent --save
//or
yarn add concent
import { run } from 'concent';
run({
counter: {
state: { num: 1, numBig: 100 },
}
});
import { register, useConcent } from 'concent';
@register('counter')
class DemoCls extends React.Component{
inc = ()=> this.setState({num: this.state.num + 1})
render(){
// 此处读取了num,表示当前实例的渲染依赖列表是 ['num']
const { num } = this.state;
// render logic
}
}
function DemoFn(){
const { state, setState } = useConcent('counter');
const inc = ()=> setState({num: state.num + 1});
// render logic
}
// privNum是一个模块里不存在的key,初始化实例时不会被模块状态所覆盖
const privState = ()=> ({privNum: 1000});
@register('counter')
class DemoCls extends React.Component{
state = privState()
render(){
const { state, moduleState } = this.ctx;// this.ctx.state === this.state
}
}
function DemoFn(){
const { state, moduleState } = useConcent({module:'counter', state:privState });
}
// good, 当showName为false时,依赖是 ['showName']
<div>{state.showName ? state.name : ''}</div>
// bad,依赖总是 ['showName', 'name']
const { showName, name } = state;
<div>{showName ? name : ''}</div>
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<div>
<ClsComp />
<FnComp />
</div>
</React.StrictMode>,
rootElement
);
run({
counter: {
state: {/** ... */},
reducer: {
inc(payload, moduleState) {
return { num: moduleState.num + 1 };
},
async asyncInc(payload, moduleState) {
await delay();
return { num: moduleState.num + 1 };
}
},
},
});
// --------- 对于类组件 -----------
changeNum = () => this.setState({ num: 10 })
// ===> 修改为
changeNum = () => this.ctx.mr.inc(10);// or this.ctx.mr.asynInc(10)
//当然这里也可以写为ctx.dispatch调用,不过更推荐用上面的moduleReducer直接调用
//this.ctx.dispatch('inc', 10); // or this.ctx.dispatch('asynInc', 10)
// --------- 对于函数组件 -----------
const { state, mr } = useConcent("counter");// useConcent 返回的就是ctx
const changeNum = () => mr.inc(20); // or ctx.mr.asynInc(10)
//对于函数组将同样支持dispatch调用方式
//ctx.dispatch('inc', 10); // or ctx.dispatch('asynInc', 10)
run({
counter: {
state: { num: 1, numBig: 100, numHuge: 10000 },
// 计算函数回调参数从左到右 (newState, oldState, fnCtx)
computed: {
numx2: ({num}) => num * 2,
numSumBig: ({num, numBig}) => num + numBig,
// 复用计算结果做二次计算
numSumBigAndHuge: ({numHuge}, o, f)=> numHuge + f.cuVal.numSumBig,
// 支持异步计算
asyncNumAdd: async({num}, o, f)=> {
f.setInitialVal(num);// 设置一个初始值,该函数只会被触发执行一次
const operand = await api.getOperand();
return num + operand;
}
},
}
});
// 类组件里获取计算结果
@register('counter')
class DemoCls extends React.Component{
render(){
// 当前依赖是['numx2', 'numSumBig']
const { numx2, numSumBig } = this.ctx.moduleComputed;
}
}
// 函数组件里获取计算结果
function DemoFn(){
const { moduleComputed } = useConcent('counter');
// 当前依赖是['numSumBigAndHuge', 'asyncNumAdd']
const { numSumBigAndHuge, asyncNumAdd } = moduleComputed;
}
// 按需读取目标计算结果
const targetVal = someCondition ? moduleComputed.numx2 : moduleComputed.numSumBig;
@register({module:'counter', connect:['foo', 'bar']})
class ClsComp extends React.Component{
render(){
const { connectedState, cr, setModuleState } = this.ctx;
const { foo, bar } = connectedState;
const changeFooState = e=> cr.foo.changeSomeKey({key: e.target.value});
}
}
function FnComp(){
const {cr, connectedState,setModuleState } = useConcent({module:'counter', connect:['foo', 'bar']});
const { foo, bar } = connectedState;
const changeFooState = e=> cr.foo.changeSomeKey({key: e.target.value});
}
run({
$$global:{// 重新全局模块状态,不写的话默认是空对象 {}
state: {theme:'green'}
}
})
// 类组件和函数组件读取globalState的目标状态
const { globalState } = this.ctx;
const { globalState } = useConcent();
import { getState, setState } from "concent";
console.log(getState('counter').num);// log: 1
setState('counter', {num:10});// 修改counter模块num值
console.log(getState('counter').num);// log: 10
import { getState, dispatch } from "concent";
(async ()=>{
console.log(getState("counter").num);// log 1
await dispatch("counter/inc");
console.log(getState("counter").num);// log 2
await dispatch("counter/asyncInc");
console.log(getState("counter").num);// log 3
})()
import { getState, reducer as ccReducer } from "concent";
(async ()=>{
console.log(getState("counter").num);// log 1
await ccReducer.counter.inc();
console.log(getState("counter").num);// log 2
await ccReducer.counter.asyncInc();
console.log(getState("counter").num);// log 3
})()
const setup = ctx=>{
// 监听普通事件
ctx.on('evName', (p1, p2)=>{/** 处理业务 */});
// 监听带id的事件,适用于有多个实例的组件,需按业务id触发具体回调时
ctx.on(['identityEvName', ctx.props.id], (p1, p2)=>{/** 处理业务 */})
}
import { emit } from 'concent';
emit('someEvent', 1, 2 )
emit(['someIdentityEvent', 'id1'], 1, 2 )
const setup = ctx=>{
const triggerEmit = ()=> ctx.emit('someEvent', 1, 2);
const triggerIdentityEmit = ()=> ctx.emit('someIdentityEvent', 1, 2);
// 打包返回到settings里给ui绑定
return { triggerEmit, triggerIdentityEmit};
}
import { run, useConcent } from "concent";
run();// 启动concent
const setup = ctx => {
const { initState, computed, watch, setState, state } = ctx;
// 初始化实例状态
initState({ count: 0 });
// 定义计算函数,这里的依赖是count,仅当count变化,触发此函数重新计算
computed("doubleCount", n => n.count * 2);
// 定义观察函数,这里的依赖是count,仅当count变化,会弹此提示
watch("count", (n, o) => alert(`from ${o.count} to ${n.count}`));
// 打包方法返回,将被收集到settings里
return {
inc: () => setState({ count: state.count + 1 }),
dec: () => setState({ count: state.count - 1 })
};
};
function Counter(){
// 这些是从实例上下文里解构出来的属性
const { state, refComputed, settings } = useConcent({ setup });
return (
<>
<h1>{state.count}</h1>
<h1>{refComputed.doubleCount}</h1>
<button onClick={settings.inc}>inc</button>
<button onClick={settings.dec}>dec</button>
</>
);
}
const setup = ctx => {
ctx.effect(() => {/**业务代码*/}, ["num"]);// 声明依赖名称即可
ctx.effect(() => {
return () => {
// 返回一个清理函数, 等价于componentWillUnmout
};
}, []);// 依赖为空数组,仅在首次渲染完毕后执行一次
};
function Counter() {
const { state, moduleComputed, moduleReducer, settings } =
useConcent({ setup, module: "counter" });
return <div> ui ... </div>
}
@register({ setup, module: "counter" })
class ClassCounter extends React.Component{
render(){
const { state, moduleComputed, moduleReducer, settings } = this.ctx;
return <div> ui ... </div>
}
}