【问题标题】:How to render a component when state and props are changed?当 state 和 props 改变时如何渲染组件?
【发布时间】:2023-04-03 17:23:01
【问题描述】:

我需要显示props 值(这是一个简单的字符串)。每次我得到新的搜索结果时,我都会发送props
在第一次渲染时,props 将始终为 undefined

编辑:
头文件.jsx

function Header() {
  const [searchString, setString] = useState('');
      
  const onChangHandler = (e) => {
    setString(e.target.value);
  };
  
  const activeSearch = () => {
    if (searchString.length > 0) {
      <Home searchResults={searchString} />;
    }
  };

  return (
    <div>
        <input
          placeholder='Search here'
          value={searchString}
          onChange={(e) => onChangHandler(e)}
        />
        <button onClick={activeSearch}>Search</button>
      </header>
    </div>
  );
}

我搜索了以前的 stackoverflow 问题和 reactjs.org,但没有找到答案。

Home.jsx

import React, { useEffect, useState } from 'react';

function Home({ searchResults }) {
  const [itemSearchResults, setResults] = useState([]);
  const [previousValue, setPreviousValue] = useState();

  // What function will re-render when the props are first defined or changed ? 
   useEffect(() => { // Doesn't work
    setResults(searchResults);
   }, [searchResults]);
           
  return (
    <div>
      <h3>Home</h3>
      <h1>{itemSearchResults}</h1>
    </div>
  );
}

export default Home;

App.js

function App() {
  return (
    <div className='App'>
      <Header />
      <Home />
      <Footer />
    </div>
  );
}

我发送输入字符串只是为了检查props 是否会在子组件(“Home”)处发生变化。

这里有高手知道是什么问题吗?

【问题讨论】:

  • 你的道具是props吗?如果不是,那么您不应该在函数参数中对其进行解构。
  • @AreebKhan 我的道具被命名为“searchResults”。
  • 你不需要做任何事情,当 props 改变时 React 会自己重新渲染。虽然如果你是 mutating the props or state (anti-pattern),React 在比较之前的 props 或 state 时可能看不到任何变化。
  • 也就是说,目前尚不清楚您要实现什么,什么不起作用,以及它是如何在您身边实施的 (symptoms of the XY problem)。请附上minimal reproducible example
  • @EmileBergeron 我解决了我的问题,我认为现在更清楚了。

标签: javascript reactjs react-hooks


【解决方案1】:

为什么它不起作用?

这是因为Home组件从未使用过,即使它包含在以下sn-p中:

const activeSearch = () => {
  if (searchString.length > 0) {
    <Home searchResults={searchString} />;
  }
};

activeSearch 函数有几个问题:

  • 虽然它使用 JSX,但它被用作事件处理程序(在渲染阶段之外)
  • 它不返回 JSX(在渲染阶段仍然会失败)

JSX 只能在 React 生命周期的渲染阶段使用。任何事件处理程序都存在于这个阶段之外,因此它可能使用的任何 JSX 都不会最终出现在最终树中。

数据决定了要渲染的内容

也就是说,the solution is to use the state 是为了知道在渲染阶段要渲染什么。

function Header() {
  const [searchString, setString] = useState('');
  const [showResults, setShowResults] = useState(false);

  const onChangHandler = (e) => {
    // to avoid fetching results for every character change.
    setShowResults(false);
    setString(e.target.value);
  };

  const activeSearch = () => setShowResults(searchString.length > 0);

  return (
    <div>
        <input
          value={searchString}
          onChange={(e) => onChangHandler(e)}
        />
        <button onClick={activeSearch}>Search</button>
        {showResults && <Home query={searchString} />}
    </div>
  );
}

useEffect根据道具变化触发效果

然后,Home 组件可以通过useEffect 触发对某个服务的新搜索请求。

function Home({ query }) {
  const [results, setResults] = useState(null);

  useEffect(() => {
    let discardResult = false;

    fetchResults(query).then((response) => !discardResult && setResults(response));

    // This returned function will run before the query changes and on unmount.
    return () => {
      // Prevents a race-condition where the results from a previous slow
      // request could override the loading state or the latest results from
      // a faster request.
      discardResult = true;

      // Reset the results state whenever the query changes.
      setResults(null);
    }
  }, [query]);

  return results ? (
    <ul>{results.map((result) => <li>{result}</li>))}</ul>
  ) : `Loading...`;
}

确实,通过useEffectthe article highlights 一样将一些状态与props 同步并不是最优的:

useEffect(() => {
  setInternalState(externalState);
}, [externalState]);

...但在我们的例子中,我们并没有同步状态,我们实际上是在触发一个效果(获取结果),这正是 useEffect 存在的原因。

const { useState, useEffect } = React;

const FAKE_DELAY = 5; // seconds

function Home({ query }) {
  const [results, setResults] = useState(null);
  
  useEffect(() => {
    let queryChanged = false;
    
    console.log('Fetch search results for', query);
    
    setTimeout(() => {
      
      if (queryChanged) {
        console.log('Query changed since last fetch, results discarded for', query);
        return;
      }
      setResults(['example', 'result', 'for', query])
    }, FAKE_DELAY * 1000);
    
    return () => {
      // Prevent race-condition
      queryChanged = true;
      setResults(null);
    };
  }, [query]);
  
  return (
    <div>
      {results ? (
        <ul>
          {results.map((result) => (
            <li>{result}</li>
          ))}
        </ul>
      ) : `Loading... (${FAKE_DELAY} seconds)`}
    </div>
  );
}

function Header() {
  const [searchString, setString] = useState('');
  const [showResults, setShowResults] = useState(false);

  const onChangHandler = (e) => {
    // to avoid fetching results for every character change.
    setShowResults(false);
    setString(e.target.value);
  };

  const activeSearch = () => setShowResults(searchString.length > 0);

  return (
    <div>
        <input
          placeholder='Search here'
          value={searchString}
          onChange={(e) => onChangHandler(e)}
        />
        <button onClick={activeSearch}>Search</button>
        {showResults && <Home query={searchString} />}
    </div>
  );
}

ReactDOM.render(<Header />, document.querySelector("#app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>

<div id="app"></div>

更好的解决方案:不受控制的输入

在您的情况下,另一种技术是通过使用ref 来使用uncontrolled &lt;input&gt;,并且仅在单击按钮而不是更改输入值时更新搜索字符串。

function Header() {
  const [searchString, setString] = useState('');
  const inputRef = useRef();

  const activeSearch = () => {
    setString(inputRef.current.value);
  }

  return (
    <div>
        <input ref={inputRef} />
        <button onClick={activeSearch}>Search</button>
        {searchString.length > 0 && <Home query={searchString} />}
    </div>
  );
}

const { useState, useEffect, useRef } = React;

const FAKE_DELAY = 5; // seconds

function Home({ query }) {
  const [results, setResults] = useState(null);
  
  useEffect(() => {
    let queryChanged = false;
    
    console.log('Fetch search results for', query);
    
    setTimeout(() => {
      
      if (queryChanged) {
        console.log('Query changed since last fetch, results discarded for', query);
        return;
      }
      setResults(['example', 'result', 'for', query])
    }, FAKE_DELAY * 1000);
    
    return () => {
      // Prevent race-condition
      queryChanged = true;
      setResults(null);
    };
  }, [query]);
  
  return (
    <div>
      {results ? (
        <ul>
          {results.map((result) => (
            <li>{result}</li>
          ))}
        </ul>
      ) : `Loading... (${FAKE_DELAY} seconds)`}
    </div>
  );
}

function Header() {
  const [searchString, setString] = useState('');
  const inputRef = useRef();

  const activeSearch = () => {
    setString(inputRef.current.value);
  }

  return (
    <div>
        <input
          placeholder='Search here'
          ref={inputRef}
        />
        <button onClick={activeSearch}>Search</button>
        {searchString.length > 0 && <Home query={searchString} />}
    </div>
  );
}

ReactDOM.render(<Header />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>

<div id="app"></div>

传递状态

[下一行]将Home组件带入Header组件内,造成重复

 {searchString.length > 0 && <Home query={searchString} />}

为了使Header 组件可重用,最快的方法是lift the state up

// No state needed in this component, we now receive
// a callback function instead.
function Header({ onSubmit }) {
  const inputRef = useRef();

  const activeSearch = () => {
    // Uses the callback function instead of a state setter.
    onSubmit(inputRef.current.value);
  }

  return (
    <div>
        <input ref={inputRef} />
        <button onClick={activeSearch}>Search</button>
    </div>
  );
}

function App() {
  // State lifted up to the parent (App) component.
  const [searchString, setString] = useState('');

  return (
    <div className='App'>
       <Header onSubmit={setString} />
       {searchString.length > 0 && <Home query={searchString} />}
       <Footer />
    </div> 
  );
}

如果该解决方案仍然过于有限,还有其他方法可以传递数据,这些方法可能会偏离主题,在此答案中全部提出,因此我将链接更多信息:

【讨论】:

  • 您好,Emile,感谢您的回复和您的帮助意愿。此行代码 - {searchString.length &gt; 0 &amp;&amp; &lt;Home query={searchString} /&gt;}Home 组件带入 Header 组件内,这会重复,我不需要。使用 `const inputRef = useRef();` 非常好。我想发送道具不显示Header中的其他组件。
  • @Sunny 在这种情况下,您需要lift the state up
  • 我的意思是页面看起来像https://www.iherb.com/。是否可以发送不在 return{} 内的道具?为了更好地解释我的问题 - 我的 App.js 看起来像这样 - function App() { return ( &lt;div className='App'&gt; &lt;Header /&gt; &lt;Home /&gt; &lt;Footer /&gt; &lt;/div&gt; ); }
  • 我还在我的答案中添加了一个包含您的代码的示例,以便更清楚我的意思。
  • @Sunny 请注意,这些新问题与问题线程无关,因此如果您有任何新问题,您应该通过通常的开发过程,如果失败,请提出一个新问题。也就是说,state isn't updated synchronously,所以这可能就是你正在经历的。
【解决方案2】:

如果您的 props 作为 searchResults 传递,则将 props 更改为,

function Home({ searchResults}) {...}

并使用

useEffect(() => { // code, function },[searchResults]) ).

【讨论】:

  • 我更改了:function Home({ searchResults}) {...} 并做了 - useEffect(() =&gt; { setResults(searchResults); },[searchResults]) ) ,但什么也没发生,新道具更改后没有渲染。
猜你喜欢
  • 1970-01-01
  • 2018-12-04
  • 2021-04-26
  • 2019-07-12
  • 2021-04-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-28
相关资源
最近更新 更多