likes
comments
collection
share

如何在 React Hook 使用双向绑定

作者站长头像
站长
· 阅读数 13

如何在 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>
    </>
  );
};

我们来看一下实际效果

如何在 React Hook 使用双向绑定

看起来还不错!

如何在 React Hook 使用双向绑定

然后是对象的处理,我们只需要遍历对象属性,给每个属性都加上 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>
    </>
  );
};

如何在 React Hook 使用双向绑定

此处应有掌声!!!

如何在 React Hook 使用双向绑定

最后就是数组的处理了,首先要对数组的一些方法进行重写

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>
    </>
  );
};

如何在 React Hook 使用双向绑定

如何在 React Hook 使用双向绑定

结束语

我这里只处理了一层,嵌套的情况就需要你们思考了(能力不足啊 😢),感谢大家。

如何在 React Hook 使用双向绑定