重磅宣布, concent 2.0发布, 依赖收集&惰性计算

夜已经很深了,这个时间宣布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>
    }
}

它们都将自动收集到相关的依赖,同时如果你强行修改状态的值,将会静默失败并给出警告,让你必需用setStatedispatch去修改(注sync系列api是setState的包装语法糖,不算违反规则)

点击这里查看此示例

注意新版React为了安全的启用异步渲染,在开发模式有double-invoking双调用检查机制(生产模式不会有此情况),所以示例里class有一次多余的渲染是正常现象。

gif图在线示例

延迟计算之方法获取 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.*版本.

⚖ 其他帮助你快速了解的在线示例

原文链接:juejin.im

上一篇:函数式夜点心:异步流程与 Task 函子
下一篇:关于继承apply和call的区别以及apply的巧妙用法

相关推荐

  • 🔥 Promise|async|Generator 实现&amp;原理大解析 | 9k字

    笔者刚接触async/await时,就被其暂停执行的特性吸引了,心想在没有原生API支持的情况下,await居然能挂起当前方法,实现暂停执行,我感到十分好奇。好奇心驱使我一层一层剥开有关JS异步编程的...

    7 个月前
  • 鸿蒙2.0发布,让我给大家整个活

    这里我就一个菜鸡前端工程师的视角跑一个鸿蒙的hello world吧,毕竟支持js开发 首先,你要去这个网站下ide, device.harmonyos.com/cn/ide#down… 也就...

    1 个月前
  • 高阶函数&amp;&amp;高阶组件

    高阶函数 特点: 接受函数类型的参数。 返回值是函数。 高阶函数具有可扩展性。 常见的高阶函数: 定时器 setTimeout(); setInterval() Promise(); 数组相关:...

    8 个月前
  • 革命性小程序框架 Remax 发布 2.0

    经过一段时间的开发,我们很高兴地向大家介绍支持 Web 应用构建的 Remax 2.0。 支持 Web 应用构建 得益于 Remax One 的设计,现在基于 Remax One 构建的应用可以编译到...

    6 个月前
  • 适用于 Vue 2.0 的功能强大的 Contextmenu 组件

    适用于 Vue 2.0 的 ContextMenu 组件。 中文 | English 安装 $ yarn add v-contextmenu # npm i -S v-contextmenu概览 ...

    3 年前
  • 转行学前端的第 34 天 : 了解 ECMAScript let &amp; const 声明

    我是小又又,住在武汉,做了两年新媒体,准备用 6 个月时间转行前端。 今日学习目标 昨天基于搜索学习了Array实例对象的方法 ,今天原本是准备学习以下Date 数据结构的,但是最近有看到let...

    5 个月前
  • 详解vuelidate 对于vueJs2.0的验证解决方案

    介绍 在后端项目里 比如我们的Laravel框架 对于表单验证有自己的一套validation机制 他将验证集成在FormRequest 我们只需要在我们的方法中依赖注入我们自己实例化后的验证类 当然...

    4 年前
  • 详解 useCallback &amp; useMemo

    前言 阅读本文章需要对 React hooks 中 useState 和 useEffect 有基础的了解。我的这篇文章内有大致介绍 在 React 项目中全量使用 Hooks。

    7 个月前
  • 试图加载jQuery为tampermonkey脚本

    试图加载jQuery为tampermonkey脚本 ...

    3 年前
  • 设计模式之-代理模式&amp;单例模式&amp;策略模式

    代理模式 目的 当调用方不方便直接操作某个对象,又或者希望在访问对象之前或之后做某些操作时,可以使用代理模式。 使用一个代理对象作为本体对象的替身; 调用方访问代理对象,代理对象做完某些操作后,按照...

    5 个月前

官方社区

扫码加入 JavaScript 社区