当数据类属性更改时重新渲染 React 组件

IT技术 javascript reactjs typescript class
2021-05-13 21:28:14

在我的 Typescript 应用程序中,有一个表示一些数据的类。此类正在端到端共享(前端和后端都使用它来构建数据)。它有一个名为items数字数组的属性

class Data {
  constructor() {
    this.items = [0];
  }

  addItem() {
    this.items = [...this.items, this.items.length];
  }
}

我正在尝试在我的组件中呈现这些数字,但由于修改类实例不会导致重新呈现,我必须“强制重新呈现”以使新items值呈现:

const INSTANCE = new Data();

function ItemsDisplay() {
  const forceUpdate = useUpdate(); // from react-use

  useEffect(() => {
    const interval = setInterval(() => {
      INSTANCE.addItem();
      forceUpdate(); // make it work
    }, 2000);

    return () => clearInterval(interval);
  }, []);

  return (
    <div>
      <h1>with class:</h1>
      <div>{INSTANCE.items.map(item => <span>{item}</span>)}</div>
    </div>
  );
}

虽然这有效,但它有一个主要缺点:addItem()不是对 进行的唯一修改INSTANCE这个类实际上有大约 10 到 15 个属性代表不同的数据部分。因此,forceUpdate()在发生修改的任何地方进行操作都是一场噩梦。更不用说,如果此实例将在组件外修改,我将无法将forceUpdate()更改与组件同步。

使用useState([])to表示items会解决这个问题,但正如我所说的Data有很多属性,一些功能也是如此。那是另一个噩梦。


我想知道从类实例渲染数据的最佳方式是什么,无需重新渲染黑客或将整个实例解包到本地组件状态。

谢谢!

这是一个 Codesandbox 演示,显示了使用类和本地状态之间的区别。

1个回答

下面是一个示例,说明如何使 Data 实例可观察并在组件中使用 Effect 来观察 Data 实例项中的变化:

const { useState, useEffect } = React;
class Data {
  constructor() {
    this.data = {
      users: [],
      products: [],
    };
    this.listeners = [];
  }

  addItem(type, newItem) {
    this.data[type] = [...this.data[type], newItem];
    //notify all listeners that something has been changed
    this.notify();
  }
  addUser(user) {
    this.addItem('users', user);
  }
  addProduct(product) {
    this.addItem('products', product);
  }
  reset = () => {
    this.data.users = [];
    this.data.products = [];
    this.notify();
  };
  notify() {
    this.listeners.forEach((l) => l(this.data));
  }
  addListener = (fn) => {
    this.listeners.push(fn);
    //return the remove listener function
    return () =>
      (this.listeners = this.listeners.filter(
        (l) => l !== fn
      ));
  };
}
const instance = new Data();
let counter = 0;
setInterval(() => {
  if (counter < 10) {
    if (counter % 2) {
      instance.addUser({ userName: counter });
    } else {
      instance.addProduct({ productId: counter });
    }
    counter++;
  }
}, 500);
//custom hook to use instance
const useInstance = (instance, fn = (id) => id) => {
  const [items, setItems] = useState(fn(instance.data));
  useEffect(
    () =>
      instance.addListener((items) => setItems(fn(items))),
    [instance, fn]
  );
  return items;
};
const getUsers = (data) => data.users;
const getProducts = (data) => data.products;
const Users = () => {
  const users = useInstance(instance, getUsers);
  return <pre>{JSON.stringify(users)}</pre>;
};
const Products = () => {
  const products = useInstance(instance, getProducts);
  return <pre>{JSON.stringify(products)}</pre>;
};
const App = () => {
  const reset = () => {
    instance.reset();
    counter = 0;
  };
  return (
    <div>
      <button onClick={reset}>Reset</button>
      <div>
        users:
        <Users />
      </div>
      <div>
        products:
        <Products />
      </div>
    </div>
  );
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>


<div id="root"></div>