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 相关文章推荐
jQuery live
May 15 Javascript
JS增加行复制行删除行的实现代码
Nov 09 Javascript
JavaScript中Number.MIN_VALUE属性的使用示例
Jun 04 Javascript
d3.js实现立体柱图的方法详解
Apr 28 Javascript
PHP实现记录代码运行时间封装类实例教程
May 08 Javascript
JavaScript程序设计高级算法之动态规划实例分析
Nov 24 Javascript
vue项目中api接口管理总结
Apr 20 Javascript
使用vue-cli3新建一个项目并写好基本配置(推荐)
Apr 24 Javascript
使用 Vue cli 3.0 构建自定义组件库的方法
Apr 30 Javascript
javascript sort()对数组中的元素进行排序详解
Oct 13 Javascript
python虚拟环境 virtualenv的简单使用
Jan 21 Javascript
微信小程序 button样式设置为图片的方法
Jun 19 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
漫威DC即将合作联动,而双方早已经秘密开始
2020/04/09 欧美动漫
在线竞拍系统的PHP实现框架(一)
2006/10/09 PHP
如何用phpmyadmin设置mysql数据库用户的权限
2012/01/09 PHP
PHP函数http_build_query使用详解
2014/08/20 PHP
跟我学Laravel之配置Laravel
2014/10/15 PHP
PHP实现的二分查找算法实例分析
2017/12/19 PHP
PHP快速导出百万级数据到CSV或者EXCEL文件
2020/11/27 PHP
JavaScript 函数式编程的原理
2009/10/16 Javascript
基于jQuery的合并表格中相同文本的相邻单元格的代码
2011/04/06 Javascript
JavaScript作用域与作用域链深入解析
2013/12/06 Javascript
对jQuary选择器的全面总结
2016/06/20 Javascript
jQuery文字轮播特效
2017/02/12 Javascript
Vue.JS项目中5个经典Vuex插件
2017/11/28 Javascript
深入浅析Vue中的Prop
2018/06/10 Javascript
d3.js实现图形缩放平移
2019/12/19 Javascript
vue npm install 安装某个指定的版本操作
2020/08/11 Javascript
Django1.3添加app提示模块不存在的解决方法
2014/08/26 Python
python遍历目录的方法小结
2016/04/28 Python
对pandas通过索引提取dataframe的行方法详解
2019/02/01 Python
Python从函数参数类型引出元组实例分析
2019/05/28 Python
导入tensorflow:ImportError: libcublas.so.9.0 报错
2020/01/06 Python
带你认识HTML5中的WebSocket
2015/05/22 HTML / CSS
MyFrenchPharma中文网:最大的法国药妆平台
2016/10/07 全球购物
Sunglasses Shop荷兰站:英国最大的太阳镜独立在线零售商和供应商
2017/01/08 全球购物
Vrbo西班牙:预订您的度假公寓(公寓、乡村房屋…)
2020/04/27 全球购物
软件工程师面试题
2012/06/25 面试题
企业管理专业个人求职信范文
2013/09/24 职场文书
高中自我评价范文
2014/01/27 职场文书
技能竞赛活动方案
2014/02/21 职场文书
学习礼仪心得体会
2014/09/01 职场文书
2015年超市员工工作总结
2015/05/04 职场文书
原生CSS实现文字无限轮播的通用方法
2021/03/30 HTML / CSS
HTML速写之Emmet语法规则的实现
2021/04/07 HTML / CSS
css3 利用transform-origin 实现圆点分布在大圆上布局及旋转特效
2021/04/29 HTML / CSS
用Python selenium实现淘宝抢单机器人
2021/06/18 Python
springboot为异步任务规划自定义线程池的实现
2022/06/14 Java/Android