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 相关文章推荐
关于UTF-8的客户端用AJAX方式获取GB2312的服务器端乱码问题的解决办法
Nov 30 Javascript
IFrame跨域高度自适应实现代码
Aug 16 Javascript
重写javascript中window.confirm的行为
Oct 21 Javascript
javascript游戏开发之《三国志曹操传》零部件开发(四)用地图块拼成大地图
Jan 23 Javascript
JS正则表达式验证数字代码
Jan 28 Javascript
基于jQuery实现响应式圆形图片轮播特效
Nov 25 Javascript
Ext JS 实现建议词模糊动态搜索功能
May 13 Javascript
详解如何构建Promise队列实现异步函数顺序执行
Oct 23 Javascript
解决echarts的多个折现数据出现坐标和值对不上的问题
Dec 28 Javascript
Vue混入mixins滚动触底的方法
Nov 22 Javascript
Vue中的this.$options.data()和this.$data用法说明
Jul 26 Javascript
Vue实现腾讯云点播视频上传功能的实现代码
Aug 17 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中常量,变量的作用域和生存周期
2013/08/10 PHP
PHP图片处理之使用imagecopyresampled函数实现图片缩放例子
2014/11/19 PHP
php使用curl出现Expect:100-continue解决方法
2015/03/03 PHP
PHP可变变量学习小结
2015/11/29 PHP
分享PHP-pcntl 实现多进程代码
2016/09/30 PHP
TopList标签和JavaScript结合两例
2007/08/12 Javascript
jQuery EasyUI API 中文文档 - ComboGrid 组合表格
2011/10/13 Javascript
Javascript开发之三数组对象实例介绍
2012/11/12 Javascript
js中for in的用法示例解析
2013/12/25 Javascript
让table变成exls的示例代码
2014/03/24 Javascript
JavaScript中的原型和继承详解(图文)
2014/07/18 Javascript
jQuery中addClass()方法用法实例
2015/01/05 Javascript
javascript事件绑定学习要点
2016/03/09 Javascript
用JavaScript获取页面文档内容的实现代码
2016/06/10 Javascript
总结AngularJS开发者最常犯的十个错误
2016/08/31 Javascript
JS实现隔行换色的表格排序
2017/03/27 Javascript
underscore之Chaining_动力节点Java学院整理
2017/07/10 Javascript
vue中如何动态绑定图片,vue中通过data返回图片路径的方法
2018/02/07 Javascript
jQuery 函数实例分析【函数声明、函数表达式、匿名函数等】
2020/05/19 jQuery
编写Python脚本抓取网络小说来制作自己的阅读器
2015/08/20 Python
Python内置函数——__import__ 的使用方法
2017/11/24 Python
浅析python中numpy包中的argsort函数的使用
2018/08/30 Python
python浪漫表白源码
2019/04/05 Python
Python数据类型之列表和元组的方法实例详解
2019/07/08 Python
pytorch 修改预训练model实例
2020/01/18 Python
TensorFlow实现打印每一层的输出
2020/01/21 Python
Django框架实现在线考试系统的示例代码
2020/11/30 Python
HTML5的结构和语义(4):语义性的内联元素
2008/10/17 HTML / CSS
世界经理人咨询有限公司面试
2014/09/23 面试题
经典演讲稿开场白
2014/08/25 职场文书
2014年学习委员工作总结
2014/11/14 职场文书
教师个人培训总结
2015/02/11 职场文书
2015年村党支部工作总结
2015/04/30 职场文书
校车安全管理责任书
2015/05/11 职场文书
《百分数的认识》教学反思
2016/02/19 职场文书
golang在GRPC中设置client的超时时间
2021/04/27 Golang