如何在 React Hook 使用双向绑定
实现思路
主要的实现方式参考 Vue2(你们也可以试试 Vue3 的 Proxy),通过 Object.defineProperty的 getter/setter 对收集的依赖项进行监听,在属性修改时调用setState,进而更新视图数据。 数组的话也借鉴了 Vue2 ,通过重写数组的 push、pop、shift、unshift、splice、sort、reverse 方法,来达到监听数组改变的效果。
实现步骤
首先需要定义一个自定义 Hook ,方便后续使用。这里定义的 setState 就是为了后续触发 React 的重渲染。
function useSignal(val) {
const ref = useRef(val);
const [, setState] = useState({});
}
接着我们需要监听传进来的变量,这里分三种情况 基本类型、对象和数组来进行处理。 首先是基本类型,由于 Object.defineProperty 监听的是对象里的属性,所以对于基本类型我们需要包装一个对象来返回,就像这样
if (typeof val !== 'object')
return {
get value() {
return ref.current;
},
set value(val) {
if (val === ref.current) return;
ref.current = val;
setState({});
}
};
使用起来也很简单,像这样
const Demo = () => {
const count = useSignal(1);
return (
<>
<div>数量:{count.value}</div>
<Button onClick={() => (count.value += 1)}>+</Button>
<Button onClick={() => (count.value -= 1)}>-</Button>
</>
);
};
我们来看一下实际效果
看起来还不错!
然后是对象的处理,我们只需要遍历对象属性,给每个属性都加上 get 和 set 方法就行了,哎哟,不错喔。
if (Object.prototype.toString.call(val) === '[object Object]') {
const init = cloneDeep(val);
Object.keys(init).forEach((key) => {
Object.defineProperty(init, key, {
get() {
return ref.current[key];
},
set(val) {
if (val === ref.current[key]) return;
ref.current[key] = val;
setState({});
}
});
});
return init;
}
写个demo试一试
const Demo = () => {
const count = useSignal({ name: '章三', value: 1 });
return (
<>
<div>姓名:{count.name}</div>
<div>得分:{count.value}</div>
<Button onClick={() => (count.name += '1')}>change name</Button>
<Button onClick={() => (count.value += 1)}>change value</Button>
</>
);
};
此处应有掌声!!!
最后就是数组的处理了,首先要对数组的一些方法进行重写
const arrayMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];
if (Array.isArray(val)) {
const init = cloneDeep(val);
arrayMethods.forEach((method) => {
const _fn = Array.prototype[method];
init[method] = function (...args) {
_fn.call(this, ...args);
setState({});
};
});
return init;
}
那如果直接改变数组的某一项呢,该怎么处理呢,我试了一下遍历数组然后每一项都用 Object.defineProperty 来进行拦截是可行的,但如果数组过长是不是有点浪费呢。所以还是给数组挂载一个 set 方法,在 set 里统一进行处理。再完善一下代码
if (Array.isArray(val)) {
const init = cloneDeep(val);
arrayMethods.forEach((method) => {
const _fn = Array.prototype[method];
init[method] = function (...args) {
_fn.call(this, ...args);
setState({});
};
});
init['set'] = (index, val) => {
if (init[index] === val) return;
init[index] = val;
setState({});
};
return init;
}
看起来好像没啥问题,让我们运行一下
const Demo = () => {
const count2 = useSignal([1, 2, 3]);
return (
<>
<div>count2: {count2.join(',')}</div>
<Button onClick={() => count2.push(parseInt(Math.random() * 10))}>add count2</Button>
<Button onClick={() => count2.pop()}>del count2</Button>
<Button onClick={() => count2.set(2, parseInt(Math.random() * 10))}>set Arr[2]</Button>
</>
);
};
结束语
我这里只处理了一层,嵌套的情况就需要你们思考了(能力不足啊 😢),感谢大家。
转载自:https://juejin.cn/post/7203174304392495165