这两天看了ahooks,有一个useReactive,看源码学习了一下,是通过proxy实现的。
今天用Object.defineProperty
尝试实现一下,效果如下
应用时代码
import React from "react";
import useReactive from "../../hooks/useReactive";
function Demo() {
const data: {
inputVal: string;
obj: {
value: string;
};
vdata: any;
count: number;
arr: any[];
} = {
inputVal: "",
obj: {
value: ""
},
vdata: null,
count: 1,
arr: []
};
const data2: any[] = ["fasdfs"];
const state = useReactive(data);
const state2 = useReactive(data2);
console.log("state2", state2);
return (
<div>
<p> state.count:{state.count}</p>
<button
onClick={() => {
state.count++;
}}
>
count ++
</button>
<button
onClick={() => {
state.count--;
}}
>
count --
</button>
<p style={{ marginTop: 20 }}> state.inputVal: {state.inputVal}</p>
<input onChange={e => (state.inputVal = e.target.value)} />
<p style={{ marginTop: 20 }}> state.obj.value: {state.obj.value}</p>
<input onChange={e => (state.obj.value = e.target.value)} />
<p style={{ marginTop: 20 }}>
state.vdata: {JSON.stringify(state.vdata)}
</p>
<button
onClick={() => {
state.vdata = {
p1: 1,
p2: 2
};
setTimeout(() => {
state.vdata.p1 = "p1";
}, 3000);
}}
>
vdata本来是null, 测试设置为对象后,是否也有响应能力
</button>
<div style={{ marginTop: 20 }}>
{" "}
state.arr:{" "}
{state.arr.map((o, index) => (
<div key={index}>{o}</div>
))}
</div>
<button
style={{ marginTop: "10px" }}
onClick={() => state.arr.push(Math.floor(Math.random() * 100))}
role="pushbtn"
>
push
</button>
<button
style={{ marginTop: "10px" }}
onClick={() => state.arr.pop()}
role="popbtn"
>
pop
</button>
<button
style={{ marginTop: "10px" }}
onClick={() => state.arr.shift()}
role="shiftbtn"
>
shift
</button>
<button
style={{ marginTop: "10px" }}
role="unshiftbtn"
onClick={() => state.arr.unshift(Math.floor(Math.random() * 100))}
>
unshift
</button>
<button
style={{ marginTop: "10px" }}
role="reverse"
onClick={() => state.arr.reverse()}
>
reverse
</button>
<button
style={{ marginTop: "10px" }}
role="sort"
onClick={() => state.arr.sort()}
>
sort
</button>
<div style={{ marginTop: 20 }}>
{" "}
state2:{" "}
{state2.map((o, index) => (
<div key={index}>{JSON.stringify(o)}</div>
))}
</div>
<button
style={{ marginTop: "10px" }}
onClick={() => {
state2.push({
aaa: "aaa"
});
setTimeout(() => {
state2.length && (state2[1].aaa = "aaa 哈哈");
}, 2000);
}}
role="pushbtn"
>
初始化就是一个数组 push, push后是个对象 ,对象一样是响应式的
</button>
</div>
);
}
export default Demo;
源码
import { useRef, useMemo, useState } from 'react'
const RE_RENDER_ATTR = '__rerender__'
let oldArrayMethods = Array.prototype
const arrayMethods = Object.create(oldArrayMethods)
const ARRAY_METHODS = [
'push',
'shift',
'unshift',
'pop',
'sort',
'splice',
'reverse'
]
ARRAY_METHODS.forEach(method=>{
arrayMethods[method] = function (...args: any[]) {
const result = oldArrayMethods[method].apply(this, args)
let inserted
let rerender = this[RE_RENDER_ATTR];
switch (method) {
case 'push':
case 'unshift':
inserted = args;
break;
case 'splice':
inserted = args.slice(2)
default:
break;
}
inserted?.forEach((isertItem: any) => {
observe(isertItem, rerender)
})
rerender()
console.log('got u')
return result
}
})
function def<T extends object>(data: T, key: string, value: any){
Object.defineProperty(data,key,{
enumerable:false,
configurable:false,
value
})
}
function defineReactive<T extends object>(obj: T, attr: string, value: any, cb: () => void) {
observe(value, cb)
Object.defineProperty(obj, attr, {
enumerable: true,
configurable: true,
get: () => {
return value
},
set: (newVal) => {
value = newVal
if (Array.isArray(newVal)) {
observeArray(newVal, cb)
} else {
observe(value, cb)
}
cb()
}
})
}
function observeArray (arr: any[], cb: () => void) {
def(arr, RE_RENDER_ATTR, cb)
// @ts-ignore
arr.__proto__ = arrayMethods
for(let i = 0; i < arr.length;i++){
observe(arr[i], cb)
}
}
function observe<T extends object> (data: T, cb: () => void) {
if (!data || typeof data !== 'object') {
return
}
Object.keys(data).forEach(attr => {
if (Array.isArray(data[attr])) {
observeArray(data[attr], cb)
} else {
defineReactive(data, attr, data[attr], cb)
}
})
}
export default function useReactive<S extends object> (initialState: S): S {
const stateRef = useRef<S>(initialState)
const [, setFlag] = useState({});
useMemo(() => {
if (Array.isArray(stateRef.current)) {
observeArray(stateRef.current, () => {
setFlag({})
})
} else {
observe(stateRef.current, () => {
setFlag({})
})
}
}, [])
return stateRef.current
}