Last Updated 2019-09-22 15:00:44

模块computed

computed定义当各个stateKey的值发生变化时,要触发的计算函数,并将其结果缓存起来,仅当stateKey的值再次变化时,才会触发计算。

type ComputedFn = (
  oldVal:any,
  newVal:any, 
  fnCtx:FnCtx,
)=> any;

type ComputedFnDesc = {
  fn: ComputedFn,
  compare?: boolean,
  depKeys?: string[],
}

type ComputedValueDef = ComputedFn | ComputedFnDesc;

读者可fork此在线示例做修改来加深理解。

定义computed

我们可以在子模块配置computed属性的对象里定义计算,key就是获取计算结果的retKey,value就是computed函数computed描述体

import { run } from 'concent';

run({
  foo:{
    state: {...},
    computed:{
      firstName(firstName)=> firstName.split('').reverse().join(''),
      fullName:{
        fn:(newState)=> `${newState.firstName}_${newState.lastName}`,
        depKeys: ['firstName', 'lastName'],
      }
    }
  }
});

当然,建议的做法是为模块单独定义一个计算函数文件,导出来给模块用

// code in model/foo/computed.js
export function firstName(firstName){
  return firstName.split('').reverse().join('');
}

export const fullName = {
  fn:(newState)=> `${newState.firstName}_${newState.lastName}`,
  depKeys: ['firstName', 'lastName'],
}

computed函数

当触发计算的依赖stateKey长度只有一个,且retKey和列表里的唯一元素同名时,我们可以直接定义computed函数,而不用显式的申明stateKey依赖列表

// 推荐此写法,更简短
export function firstName(firstName){
  return firstName.split('').reverse().join('');
}

//等同于如下写法,显式的申明依赖列表,当firstName的值发生变化时,触发fn计算,结果收集到firstName下
export const firstName = {
  fn:(newState)=> `${newState.firstName}_${newState.lastName}`,
  depKeys: ['firstName'],
}

computed描述体

当触发计算的依赖stateKey有多个,或者retKey名字和长度为1的依赖stateKey里的元素不同命时,需要显示的stateKey依赖列表

//当firstName和lastName任意一个发生变化时,触发fn计算函数,结果收集到fullName下
export const fullName = {
  fn:(newState)=> `${newState.firstName}_${newState.lastName}`,
  depKeys: ['firstName', 'lastName'],
}

retKeystateKey不同名时

// code in model/foo/computed.js
export const shortName = {
  fn:(firstName)=> firstName.substr(0,5),
  depKeys: ['firstName'],
}

export const prefixedName = {
  fn:(firstName)=> `^_^${firstName}`,
  depKeys: ['firstName'],
}

/************************************************/

// code in page/foo/index.js
import { useConcent } from 'concent';

export default Foo(){
  const { moduleComputed } = useConcent('foo');
  //获取结果
  const {shortName, prefixedName} = moduleComputed;
}

当然,以上写法也可以就用firstName作为retKey来定义计算函数,这样就不用显示的申明依赖的stateKey

// code in model/foo/computed.js
export function firstName(firstName){
  return {
    shortName: firstName.substr(0,5),
    prefixedName: `^_^${firstName}`,
  }
}

/************************************************/

// code in page/foo/index.js

export default Foo(){
  const { moduleComputed } = useConcent('foo');
  //'firstName'是retKey,从它对应的值里解构出shortName, prefixedName
  const {shortName, prefixedName} = moduleComputed.firstName;
}
retKey错误定义

当stateKey依赖列表长度是1,且唯一元素的名字不和模块的stateKey同名时,不可以将retKey写为stateKey

错误定义

// 'firstName'隐含对应的depKeys是['firstName'],不可将其改写为['lastName']
export const firstName = {
  fn:(firstName)=> firstName.substr(0,5),
  depKeys: ['lastName'], // wrong !!!}

触发计算

计算的触发时机有两个

  • 模块被加载时,所有计算函数都会被触发(和该模块下有没有相关的组件被实例化没有关系)
  • 模块的某些状态被改变时,这些状态key对应的计算函数被依次触发
注意

key对应的应该是primitive类型的(如number, string, boolean),如果是object型,则需要总是返回新的引用才能触发计算

// code in models/foo/computed.js

//hobbies在模块状态里对应的值是一个数组
export function hobbies(hobbies, oldVal) {
  return hobbies.length * 2;
}

// code in models/foo/reducer.js
export function addHobby(hobby, moduleState){
  const { hobbies } = moduleState;
  hobbies.push(hobby);
  // return { hobbies };不会触发hobbies的计算函数
  return { hobbies: [...hobbies] };//正确的写法}

如果需要return { hobbies }能触发计算,可将其计算定义写为计算描述体,并设置compare为false,只要对这个key设了值就触发计算

export const hobbies = {
  fn: (hobbies) => hobbies.length * 2,
  compare: false,//不做比较,只要片段状态里对设了`hobbies`的值,就触发计算}

获取计算结果

通过实例上下文ctx.moduleComputed属性下的对象去获取,该对象的key就是computed里定义的各个retKey

  • 类组件里获取
import { register } from 'concent';

@register('foo')
class Foo extends Components{
  render(){
    const moduleComputed = this.ctx.moduleComputed;
  }
}
  • function组件里获取
import { useConcent } from 'concent';

export default Foo(){
  const { moduleComputed } = useConcent('foo');
}
  • RenderProps组件里获取
import { registerDumb } from 'concent';

registerDumb('foo')(ctx=>{
  const { moduleComputed } = ctx;
});