javascript use影响同级组件之间的执行顺序

pu3pd22g  于 2023-01-19  发布在  Java
关注(0)|答案(2)|浏览(131)

在此代码中:

import React from 'react';
import './style.css';

let Child2 = () => {
  React.useEffect(() => {
    console.log('Child 2');
  }, []);
  return <div />;
};

let Child1 = ({ children }) => {
  return <div>{children}</div>;
};

let FirstComponent = () => {
  React.useEffect(() => {
    console.log('FirstComponent');
  }, []);
  return <div />;
};

export default function App() {
  React.useEffect(() => {
    console.log('Main App');
  }, []);
  return (
    <div>
      <FirstComponent />
      <Child1>
        <Child2 />
      </Child1>
    </div>
  );
}

输出为:

FirstComponent
Child 2
Main App
    • 问题**

是否有一些可靠的来源(例如文档),以便我们可以说FirstComponentuseEffect总是在Child2?useEffect之前

    • 为什么这是相关的?**

如果我们确定来自FirstComponent的效果总是首先运行,那么在那里执行一些初始化工作(可能执行一些副作用)可能会很有用,我们希望应用中的所有其他useEffect都可以使用这些初始化工作。我们无法对普通的父/子效果执行此操作,因为您可以看到父效果("主应用")在子效果("子2")之后运行。

ruarlubt

ruarlubt1#

回答问题:据我所知,React并不保证子函数调用组件函数的顺序,不过如果它们在兄弟函数之间不是按从第一个到最后一个的顺序调用,那就太令人吃惊了(因此,在第一次调用Child1之前可靠地调用FirstComponent至少一次,但是,尽管对createElement的调用确实是按此顺序进行的,但对组件函数的调用是由React在它认为合适的时间和方式完成的。但我不认为有文件证明它们会有任何特定的顺序。
但是:
如果我们确定来自FirstComponent的效果总是首先运行,那么在那里执行一些初始化工作可能会很有用,我们希望应用中的所有其他useEffects都可以使用这些初始化工作。我们无法对普通的父/子效果执行此操作,因为您可以看到父效果("主应用")在子效果("子2")之后运行。
即使你找到文档说顺序是有保证的,我也不会这样做。兄弟组件之间的串扰不是一个好主意。这意味着组件不能彼此分开使用,使组件更难测试,而且是 * 不寻常的 *,使维护代码库的人感到惊讶。当然,你可以这样做,但通常情况下,lifting state up最有可能适用(一般情况下是"state",而不仅仅是组件状态)。相反,可以在父对象中执行任何需要执行的一次性初始化(App)-不是作为效果,而是作为父对象中的组件状态或存储在ref中的示例状态等,并通过props将其传递给子对象,context,Redux商店等。
在下面,我将通过 prop 传递给孩子们,但这只是一个例子。

通常存储子元素使用的信息的地方是父元素的状态。useState支持一个回调函数,它只在初始化期间被调用。除非你有一个很好的理由不这样做,否则这就是放置这类东西的地方。在评论中,你已经暗示你有一个很好的理由不这样做,但是如果我在将来对其他人的回答中没有提到这一点,那我就是失职。不仅仅是现在的你。
(This示例和下面的第二个示例通过 prop 将信息传递给孩子,但同样,它可以是 prop 、上下文、Redux存储等。)
示例:

const { useState, useEffect } = React;

let Child2 = () => {
    return <div>Child 2</div>;
};

let Child1 = ({ value, children }) => {
    return (
        <div>
            <div>value = {value}</div>
            {children}
        </div>
    );
};

let FirstComponent = ({ value }) => {
    return <div>value = {value}</div>;
};

function App() {
    const [value] = useState(() => {
        // Do one-time initialization here (pretend this is an
        // expensive operation).
        console.log("Doing initialization");
        return Math.floor(Math.random() * 1000);
    });

    useEffect(() => {
        return () => {
            // This is called on unmount, clean-up here if necessary
            console.log("Doing cleanup");
        };
    }, []);

    return (
        <div>
            <FirstComponent value={value} />
            <Child1 value={value}>
                <Child2 />
            </Child1>
        </div>
    );
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

从技术上讲,我想您可以使用它来执行根本不会产生任何值的操作,但从语义上讲,这将是奇怪的,我不认为我会推荐它。

非国家

如果由于某种原因,信息不能存储在state中,则可以使用ref来存储它(尽管你可能更喜欢state)或者只存储一个标志,表示"我已经完成了我的初始化"。refs的一次性初始化是非常正常的。如果初始化需要清理,你可以在一个useEffect cleanup回调函数中实现这一点。下面是一个例子(这个例子最终在ref上存储了一些标志以外的东西,但它也可以只是一个标志):
一个二个一个一个

您的具体用例示例

关于您链接的specific use case(注意:问题中的代码可能不正确;我不想在这里纠正它,尤其是因为我不使用axios):
我正在使用axios拦截器来全局处理错误,但想从拦截器设置我的应用的状态。

axios.interceptors.response.use(
   error => {
      AppState.setError(error)
   }
)

(And您已经指出了AppState(无论它是什么)只能在App中访问。)
我不喜欢修改全局axios示例,这是另一个影响页面/应用程序中使用axios所有代码的交叉问题,无论是您的代码还是库中的代码,都可能以一种不适合您的应用程序显示发生的错误状态的方式使用axios
我倾向于将拦截器与App状态更新分离:
1.有一个axios Package 器模块,用于导出定制的axios instance
1.在该示例上放置拦截器
1.提供从该模块订阅错误事件的方法
1.让App订阅其中的错误事件以设置其状态(并在卸载时取消订阅)
这听起来很复杂,但即使没有预先构建的事件发射器类,也只有大约30行代码:

import globalAxios from "axios";

const errorListeners = new Set();

export function errorSubscribe(fn) {
    errorListeners.add(fn);
}

export function errorUnsubscribe(fn) {
    errorListeners.delete(fn);
}

function useErrorListener(fn) {
    const subscribed = useRef(false);
    if (!subscribed.current) {
        subscribed.current = true;
        errorSubscribe(fn);
    }
    useEffect(() => {
        return () => errorUnsubscribe(fn);
    }, []);
}

export const axios = globalAxios.create({/*...config...*/});

instance.interceptors.response.use(() => {
    (error) => {
        for (const listener of errorListeners) {
            try { listener(error); } catch {}
        }
    };
});

然后在App中:

import { axios, useErrorListener } from "./axios-wrapper";

function App() {
    useErrorListener((error) => AppState.setError(error));
    // ...
}

在需要使用axios示例的代码中:

import { axios } from "./axios-wrapper";

// ...

这有点简单(例如,它假设您永远不会依赖于错误回调函数),但您已经了解了这个概念。

sdnqo3pr

sdnqo3pr2#

我仅次于@ T. J. Crowder,你不应该依赖组件的执行顺序来实现任何特性。
1.您想要实现的是反模式的隐式依赖关系,这会让ppl感到惊讶,但不要这样做。
1.毕竟不太靠谱,执行顺序维持了,但连续性得不到保证。
我将用React组件执行顺序的一些细节来补充他的回答。
对于一个简单的例子:

function A() {
  return (<>
      <B />
      <C>
        <D />
      </C>
    </>
  );
}

确定组件执行顺序的经验法则是从JSX元素创建调用的Angular 来考虑。在我们的例子中,它将是A(B(), C(D()))。与JS函数执行顺序相同,组件的执行(或“呈现”)顺序将是B, D, C, A
但这也伴随着警告:
1.如果任何组件被保释,例如,DReact.memo的“纯”组件,并且其属性没有改变,则顺序将是B, C, A,保持顺序,但是跳过被保释的组件。
1.不常见的异常,如SuspenseList(实验性)
<SuspenseList>协调其下最近的<Suspense>节点的“展现次序”。
其原因是通过设计影响其子节点的执行顺序。
1.在并发模式下,因为渲染可以被React随意中断,所以执行顺序的连续性是个问题。可能会发生B, D, B, D, C, A这样的情况。(也就是说,useEffect看起来不受AFAICT影响,因为它是在提交阶段调用的)

相关问题