40行代码把Vue3的响应式集成进React做状态管理


Posted in Javascript onMay 20, 2020

前言

vue-next是Vue3的源码仓库,Vue3采用lerna做package的划分,而响应式能力@vue/reactivity被划分到了单独的一个package中。

如果我们想把它集成到React中,可行吗?来试一试吧。

使用示例

话不多说,先看看怎么用的解解馋吧。

// store.ts
import { reactive, computed, effect } from '@vue/reactivity';

export const state = reactive({
 count: 0,
});

const plusOne = computed(() => state.count + 1);

effect(() => {
 console.log('plusOne changed: ', plusOne);
});

const add = () => (state.count += 1);

export const mutations = {
 // mutation
 add,
};

export const store = {
 state,
 computed: {
 plusOne,
 },
};

export type Store = typeof store;
// Index.tsx
import { Provider, useStore } from 'rxv'
import { mutations, store, Store } from './store.ts'
function Count() {
 const countState = useStore((store: Store) => {
 const { state, computed } = store;
 const { count } = state;
 const { plusOne } = computed;

 return {
  count,
  plusOne,
 };
 });

 return (
 <Card hoverable style={{ marginBottom: 24 }}>
  <h1>计数器</h1>
  <div className="chunk">
  <div className="chunk">store中的count现在是 {countState.count}</div>
  <div className="chunk">computed值中的plusOne现在是 {countState.plusOne.value}</div>
   <Button onClick={mutations.add}>add</Button>
  </div>
 </Card>
 );
}

export default () => {
 return (
 <Provider value={store}>
  <Count />
 </Provider>
 );
};

可以看出,store的定义只用到了@vue/reactivity,而rxv只是在组件中做了一层桥接,连通了Vue3和React,然后我们就可以尽情的使用Vue3的响应式能力啦。

预览

40行代码把Vue3的响应式集成进React做状态管理

可以看到,完美的利用了reactive、computed的强大能力。

分析

从这个包提供的几个核心api来分析:

effect(重点)

effect其实是响应式库中一个通用的概念:观察函数,就像Vue2中的Watcher,mobx中的autorun,observer一样,它的作用是收集依赖。

它接受的是一个函数,它会帮你执行这个函数,并且开启依赖收集,

这个函数内部对于响应式数据的访问都可以收集依赖,那么在响应式数据被修改后,就会触发更新。

最简单的用法

const data = reactive({ count: 0 })
effect(() => {
 // 就是这句话 访问了data.count
 // 从而收集到了依赖
 console.log(data.count)
})

data.count = 1
// 控制台打印出1

那么如果把这个简单例子中的

() => {
 // 就是这句话 访问了data.count
 // 从而收集到了依赖
 console.log(data.count)
}

这个函数,替换成React的组件渲染,是不是就能达成响应式更新组件的目的了?

reactive(重点)

响应式数据的核心api,这个api返回的是一个proxy,对上面所有属性的访问都会被劫持,从而在get的时候收集依赖(也就是正在运行的effect),在set的时候触发更新。

ref

对于简单数据类型比如number,我们不可能像这样去做:

let data = reactive(2)
// ?oops
data = 5

这是不符合响应式的拦截规则的,没有办法能拦截到data本身的改变,只能拦截到data身上的属性的改变,所以有了ref。

const data = ref(2)
// ?ok
data.value= 5

computed

计算属性,依赖值更新以后,它的值也会随之自动更新。其实computed内部也是一个effect。

拥有在computed中观察另一个computed数据、effect观察computed改变之类的高级特性。

实现

从这几个核心api来看,只要effect能接入到React系统中,那么其他的api都没什么问题,因为它们只是去收集effect的依赖,去通知effect触发更新。

effect接受的是一个函数,而且effect还支持通过传入schedule参数来自定义依赖更新的时候需要触发什么函数,如果我们把这个schedule替换成对应组件的更新呢?要知道在hook的世界中,实现当前组件强制更新可是很简单的:

useForceUpdate

export const useForceUpdate = () => {
 const [, forceUpdate] = useReducer(s => s + 1, 0);
 return forceUpdate;
};

这是一个很经典的自定义hook,通过不断的把状态+1来强行让组件渲染。
而rxv的核心api: useStore接受的也是一个函数selector,它会让用户自己选择在组件中需要访问的数据。

那么思路就显而易见了:

  1. 把selector包装在effect中执行,去收集依赖。
  2. 指定依赖发生更新时,需要调用的函数是当前正在使用useStore的这个组件的forceUpdate强制渲染函数。

这样不就实现了数据变化,组件自动更新吗?
简单的看一下核心实现

useStore和Provider

import React, { useContext } from 'react';
import { useForceUpdate, useEffection } from './share';

type Selector<T, S> = (store: T) => S;

const StoreContext = React.createContext<any>(null);

const useStoreContext = () => {
 const contextValue = useContext(StoreContext);
 if (!contextValue) {
 throw new Error(
  'could not find store context value; please ensure the component is wrapped in a <Provider>',
 );
 }
 return contextValue;
};

/**
 * 在组件中读取全局状态
 * 需要通过传入的函数收集依赖
 */
export const useStore = <T, S>(selector: Selector<T, S>): S => {
 const forceUpdate = useForceUpdate();
 const store = useStoreContext();

 const effection = useEffection(() => selector(store), {
 scheduler: forceUpdate,
 lazy: true,
 });

 const value = effection();
 return value;
};

export const Provider = StoreContext.Provider;

这个option是传递给Vue3的effectapi,

scheduler规定响应式数据更新以后应该做什么操作,这里我们使用forceUpdate去让组件重新渲染。

lazy表示延迟执行,后面我们手动调用effection来执行

{
 scheduler: forceUpdate,
 lazy: true,
}

再来看下useEffection和useForceUpdate

import { useEffect, useReducer, useRef } from 'react';
import { effect, stop, ReactiveEffect } from '@vue/reactivity';

export const useEffection = (...effectArgs: Parameters<typeof effect>) => {
 // 用一个ref存储effection
 // effect函数只需要初始化执行一遍
 const effectionRef = useRef<ReactiveEffect>();
 if (!effectionRef.current) {
 effectionRef.current = effect(...effectArgs);
 }

 // 卸载组件后取消effect
 const stopEffect = () => {
 stop(effectionRef.current!);
 };
 useEffect(() => stopEffect, []);

 return effectionRef.current
};

export const useForceUpdate = () => {
 const [, forceUpdate] = useReducer(s => s + 1, 0);
 return forceUpdate;
};

也很简单,就是把传入的函数交给effect,并且在组件销毁的时候停止effect而已。

流程

  1. 先通过useForceUpdate在当前组件中注册一个强制更新的函数。
  2. 通过useContext读取用户从Provider中传入的store。
  3. 再通过Vue的effect去帮我们执行selector(store),并且指定scheduler为forceUpdate,这样就完成了依赖收集。
  4. 那么在store里的值更新了以后,触发了scheduler也就是forceUpdate,我们的React组件就自动更新啦。

就简单的几行代码,就实现了在React中使用@vue/reactivity中的所有能力。

优点:

  1. 直接引入@vue/reacivity,完全使用Vue3的reactivity能力,拥有computed, effect等各种能力,并且对于Set和Map也提供了响应式的能力。后续也会随着这个库的更新变得更加完善的和强大。
  2. vue-next仓库内部完整的测试用例。
  3. 完善的TypeScript类型支持。
  4. 完全复用@vue/reacivity实现超强的全局状态管理能力。
  5. 状态管理中组件级别的精确更新。
  6. Vue3总是要学的嘛,提前学习防止失业!

缺点:

由于需要精确的收集依赖全靠useStore,所以selector函数一定要精确的访问到你关心的数据。甚至如果你需要触发数组内部某个值的更新,那你在useStore中就不能只返回这个数组本身。

举一个例子:

function Logger() {
 const logs = useStore((store: Store) => {
 return store.state.logs.map((log, idx) => (
  <p className="log" key={idx}>
  {log}
  </p>
 ));
 });

 return (
 <Card hoverable>
  <h1>控制台</h1>
  <div className="logs">{logs}</div>
 </Card>
 );
}

这段代码直接在useStore中返回了整段jsx,是因为map的过程中回去访问数组的每一项来收集依赖,只有这样才能达到响应式的目的。

源码地址:https://github.com/sl1673495/react-composition-api

到此这篇关于40行代码把Vue3的响应式集成进React做状态管理的文章就介绍到这了,更多相关Vue3 响应式集成React状态管理内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
JavaScript中“+=”的应用
Feb 02 Javascript
9个JavaScript评级/投票插件
Jan 18 Javascript
几个比较实用的JavaScript 测试及效验工具
Apr 18 Javascript
基于jQuery.Hz2Py.js插件实现的汉字转拼音特效
May 07 Javascript
Bootstrap中的Panel和Table全面解析
Jun 13 Javascript
jQuery简单注册和禁用全局事件的方法
Jul 25 Javascript
用JavaScript和jQuery实现瀑布流
Mar 19 Javascript
js常用正则表达式集锦
May 17 Javascript
JavaScript使用表单元素验证表单的示例代码
Aug 20 Javascript
layui导出所有数据的例子
Sep 10 Javascript
Vue Components 数字键盘的实现
Sep 18 Javascript
详解uniapp的全局变量实现方式
Jan 11 Javascript
Vue3 的响应式和以前有什么区别,Proxy 无敌?
May 20 #Javascript
在Angular中实现一个级联效果的下拉框的示例代码
May 20 #Javascript
vue模块移动组件的实现示例
May 20 #Javascript
vue父子组件间引用之$parent、$children
May 20 #Javascript
jQuery HTML获取内容和属性操作实例分析
May 20 #jQuery
微信小程序国际化探索实现(附源码地址)
May 20 #Javascript
jQuery HTML设置内容和属性操作实例分析
May 20 #jQuery
You might like
php字符串分割函数explode的实例代码
2013/02/07 PHP
ThinkPHP视图查询详解
2014/06/30 PHP
Yii2――使用数据库操作汇总(增删查改、事务)
2016/12/19 PHP
PHP实现登录注册之BootStrap表单功能
2017/09/03 PHP
javascript中对Attr(dom中属性)的操作示例讲解
2013/12/02 Javascript
Select标签下拉列表二级联动级联实例代码
2014/02/07 Javascript
js使用栈来实现10进制转8进制与取除数及余数
2014/06/11 Javascript
JavaScript控制图片加载完成后调用回调函数的方法
2015/03/20 Javascript
Javascript中setTimeOut和setInterval的定时器用法
2015/06/12 Javascript
javascript中caller和callee详解
2015/08/10 Javascript
jQuery使用ajax_动力节点Java学院整理
2017/07/05 jQuery
Vue源码学习之初始化模块init.js解析
2017/11/02 Javascript
基于vue中解决v-for使用报红并出现警告的问题
2018/03/03 Javascript
JavaScript笛卡尔积超简单实现算法示例
2018/07/30 Javascript
详解用Webpack与Babel配置ES6开发环境
2019/03/12 Javascript
判断“命令按钮”是否被鼠标单击详解
2019/07/31 Javascript
复习Python中的字符串知识点
2015/04/14 Python
使用Python的Tornado框架实现一个一对一聊天的程序
2015/04/25 Python
python任务调度实例分析
2015/05/19 Python
20招让你的Python飞起来!
2016/09/27 Python
python中快速进行多个字符替换的方法小结
2016/12/15 Python
Python编程实现控制cmd命令行显示颜色的方法示例
2017/08/14 Python
python selenium自动上传有赞单号的操作方法
2018/07/05 Python
python try except 捕获所有异常的实例
2018/10/18 Python
详解从Django Rest Framework响应中删除空字段
2019/01/11 Python
Python将string转换到float的实例方法
2019/07/29 Python
CSS3 3D旋转rotate效果实例介绍
2016/05/03 HTML / CSS
mysql的最长数据库名,表名,字段名可以是多长
2014/04/21 面试题
宿舍违规检讨书
2014/01/12 职场文书
八年级生物教学反思
2014/01/22 职场文书
安全目标责任书
2014/07/22 职场文书
化妆品促销活动总结
2015/05/07 职场文书
2015高中教师个人工作总结
2015/07/21 职场文书
2016年度师德标兵先进事迹材料
2016/02/26 职场文书
优秀家长事迹材料(2016推荐版)
2016/02/29 职场文书
Arthas排查Kubernetes中应用频繁挂掉重启异常
2022/02/28 MySQL