reactjs React JS:我的视图模型导致呈现的无限循环

w8f9ii69  于 2023-02-12  发布在  React
关注(0)|答案(1)|浏览(141)

我是React JS的新手,来自iOS,所以尝试实现MVVM。不确定这是不是正确的选择,但这不是我的问题。我想了解react是如何工作的,我做错了什么。下面是我的代码:

const ViewContractViewModel = () => {
  console.log('creating view model');
  const [value, setValue] = useState<string | null>(null);
  const [isLoading, setisLoading] = useState(false);

  async function componentDidMount() {
    console.log('componentDidMount');
    setisLoading(true);
    // assume fetchValueFromServer works properly
    setValue(await fetchValueFromServer());
    console.log('value fetched from server');
    setisLoading(false);
  }

  return { componentDidMount, value, isLoading };
};

export default function ViewContract() {
  const { componentDidMount, value, isLoading } = ViewContractViewModel();
  useEffect(() => {
    componentDidMount();
  }, [componentDidMount]);

  return (
    <div className='App-header'>
      {isLoading ? 'Loading' : value ? value : 'View Contract'}
    </div>
  );
}

所以我知道这里发生了什么:组件已挂载,因此我在视图模型上调用componentDidMount,这将调用setIsLoading(true),从而导致组件的重新呈现,这将导致视图模型重新初始化,然后我们调用componentDidMount,这是一个循环。
我怎样才能避免这个循环?创建视图模型的正确方法是什么?我怎样才能让代码在组件出现后执行一次?
编辑:为了使我的问题更一般化,我在这里实现MVVM的方式意味着视图模型中useState的任何声明都将在我们每次调用setXXX函数时触发一个循环,因为组件将被重新呈现,视图模型将被重新创建,useState将被重新声明。
有没有什么正确的方法?
多谢了!

bvn4nwqk

bvn4nwqk1#

React中的一个常见模式是use{NameOfController}并使其完全自包含,这样,您就不必手动调用componentDidMount,而只需在视图中处理常见的UI状态"loading"、"error"和"success"即可。
使用上面的例子,你可以编写一个可重用的控制器钩子,如下所示:

import { useEffect, useState } from "react";
import api from "../api";

export default function useViewContractViewModel() {
  const [data, setData] = useState("");
  const [isLoading, setLoading] = useState(true);
  const [error, setError] = useState("");

  useEffect(() => {
    (async () => {
      try {
        const data = await api.fetchValueFromServer();
        setData(data);
      } catch (error: any) {
        setError(error.toString());
      } finally {
        setLoading(false);
      }
    })();
  }, []);

  return { data, error, isLoading };
}

然后在视图组件中使用它:

import useViewContractViewModel from "./hooks/useViewContractViewModel";
import "./styles.css";

export default function App() {
  const { data, error, isLoading } = useViewContractViewModel();

  return (
    <div className="App">
      {isLoading ? (
        <p>Loading...</p>
      ) : error ? (
        <p>Error: {error}</p>
      ) : (
        <p>{data || "View Contract"}</p>
      )}
    </div>
  );
}

下面是一个演示:

另外,如果你想让你的控制器钩子更动态,并且可以控制初始数据集,那么你可以给它传递props,然后将其添加到useEffect依赖项数组中:

import { useEffect, useState } from "react";
import api from "../api";

export default function useViewContractViewModel(id?: number) {
  const [data, setData] = useState("");
  const [isLoading, setLoading] = useState(true);
  const [error, setError] = useState("");

  useEffect(() => {
    (async () => {
      try {
        const data = await api.fetchValueFromServer(id);
        setData(data);
      } catch (error: any) {
        setError(error.toString());
      } finally {
        setLoading(false);
      }
    })();
  }, [id]);

  return { data, error, isLoading };
}

或者,返回一个可重用的回调函数,该函数允许您在视图组件中重新获取数据:

import { useCallback, useEffect, useState } from "react";
import api from "../api";

export default function useViewContractViewModel() {
  const [data, setData] = useState("");
  const [isLoading, setLoading] = useState(true);
  const [error, setError] = useState("");

  const fetchDataById = useCallback(async (id?: number) => {
    setLoading(true);
    setData("");
    setError("");

    try {
      const data = await api.fetchValueFromServer(id);
      setData(data);
    } catch (error: any) {
      setError(error.toString());
    } finally {
      setLoading(false);
    }
  }, [])

  useEffect(() => {
   fetchDataById()
  }, [fetchDataById]);

  return { data, error, fetchDataById, isLoading };
}

相关问题