一个数据驱动的响应核心
本仓库并不提供独立的dom操作库,只提供连接符合dom接口(
react
标准)规范的dom库的嵌入api。
如果你想使用现成的框架,可以移步limbo(一个集成了调度和事件系统的高性能现代前端框架)。并且使用我们提供的mixInReact
来注册limbo
。
如果你想使用符合规范的其他框架,请使用reactivity/register
进行注册。
当然,最方便的还是使用我们提供的reabo
来作为独立的mvvm
框架使用。(实际上,它就是通过reactivity/register
注册了limbo
)
yarn add @limbo/reactivity
注意: reactivity
本身可以以一个完整的mvvm
框架形态使用,并且提供了高性能的独立diff
包及一些静态优化,但我们还是希望用户能使用标准的react
生态,所以独立dom
需要引入额外的插件.
要想在react
里使用reactivity
是非常简单的,只需要使用两行代码
import { mixInReact } from '@limbo/reactivity'
mixInReact(React)
从此,你可以快乐的在React
里使用reactivity
了.
默认的,reactivity
会为每一个React.Component
注入一个名为rootState
的对象,你可以直接操作这个对象上的任意值,并会直接得到响应
const A = (props) => {
const handleClick = () => {
props.rootState.a += 1
}
return (
<button onClick={handleClick}></button>
)
}
上面代码中,点击了按钮后,任何依赖rootState.a
的组件都会更新
你可能会问,rootState
究竟从哪里来呢?
reactivity
提供了一个注册rootState
的方法
setRootState(
createState({
a:1,
b:2,
c:{d:[1,2,3],e:1},
f:{a:1,b:2}
})
)
事实上,createState
注册了一个reactivity
概念上的State
,然后通过
setRootState
将它注册到rootState
上.
当然,如果你想像vue3
一样创建一个单值属性,你也可以使用createRef
注意,你需要使用value
来访问它。具体的,你可以参考call by value
和call by reference
的区别。
function App() {
const ref = createRef(1)
useEffect(() => {
console.log(ref.value)
})
return <Son state={ref}/>
}
作为响应式框架,watcher
应该是很常见的功能。
它随着一个响应式变量的更新而触发提前定义好的响应操作。
function App() {
const state = createState({
userId: 'init'
})
const watch = createWatcher(state,'userId',(newState,oldId,newId) => {
localStorage.setItem('id',newId)
})
}
createWatcher
的签名如下:
watcher:(state:State,key:any,onChange:IOnChange)
IOnChange:(newState: object, oldValue ?: any, newValue ?: any, changeKey ?: any) => void
同时,reactivity
提供了计算功能,你可以像任何响应式框架(vue
,mobx
)一样使用computed
function App(props) {
const computed = createComputed(() => props.state.a * props.state.b)
}
注意,createComputed
需要传入一个函数
是的,单一的rootState
非常难以管理,我们希望在任何位置都能快速的创建一个
State
,并提供响应式的能力,参考以下代码:
function App(props) {
const A = createState({aa:1})
return (
<OtherState otherState={A}/>
)
}
此时,你可以在OtherState
里直接修改A
的任意值,它也会触发响应式更新.
当你在注册一个State
时,实际上就是对一个object
进行观察,同时reactivity
也暴露出了一个watcher
的API
let obj = {a:1,b:2}
obj = watcher(obj,() => console.log('i am not side-effect'))
你可以对任意的State
(不论是自动注入,或是手动创建的)进行修改.
const A = createState([1,2,3])
A.length = 4
A[2] = 0
A.push(3)
上面的每一项都会触发响应式的更新,无需其他的心智负担.
同样的,即使在深层次的嵌套对象的情况下,它也会完美的进行响应.
const A = {
a: {
b: {
c
}
}
}
A.a.b.c = 3
reactivity
保证了当父子组件都需要更新的时候,但子组件先于父组件更新时,父组件不会再无效的reconcile
子组件。
实际上,父组件传入的props
不发生变化时,子组件不应该出现任何由父组件更新导致的变化。所以reactivity
把shallowequal
作为是否有效更新的判断标准。
实际上,更新顺序在开启schedule
后是由react
决定的,父子组件在updateQueue
中的位置并不由reactivity
计算,但框架保证了必要情况下节省无效更新。
reactivity
拥有非常好的依赖收集性能,它没有潜在的引用逃逸和闭包等问题,
旧的依赖都会在下次收集时被清除,无需担心依赖储存会导致内存泄漏.
很不幸的是,目前的响应式框架都存在非常大的问题: 轻量级的依赖收集在组件规模增大后,却会成为一个非常耗时的工作.
reactivity
并没有办法解决它,但是有很多小技巧可以提升依赖收集过程的性能
.一个很常见的方法就是,不要使用嵌套层级太深的数据结构作为State
,你应该打平而不是嵌套他们.
// good
const A = createState({a:1})
const B = createState({b:2})
const C = createState({c:3})
// bad
const nest = createState({a:{b:c}})
原因在于,你可以在任何地方创建State
,所以对嵌套对象的依赖就没有那么必要了.
上面已经提到过,依赖清除会在第二次收集时进行.它的实现是标准的double buffer
,具体细节不必深究.
在集成reactivity
后,你的React
应用不再需要使用繁琐的memo
或者PureComponent
来进行优化了,任何组件都会被自动的shallowEqual
优化.
使用响应式框架最诱人的一点就是,你不再需要使用redux
里繁琐的reselect
来进行性能提升,也不需要在每个层级组件上使用useCallback
来增加额外的心智负担.任何组件都只会在你需要的时候被精确更新!
reactivity
不会干扰任何的schedule
过程,也不会涉及到任何内部优先级的计算(统一为forceUpdate
优先级)。
实际上,框架的实现并不是表面上的mutable
,所以调度涉及到的多次更新不会影响到框架的运行,无需担心调度被框架干扰。
reactivity
提供了History
进行时光回溯,并且提供标准的chrome
插件,但目前暂未开源.