Làm cách nào để buộc thành phần hiển thị lại bằng hook trong React?


108

Xem xét ví dụ về hooks dưới đây

   import { useState } from 'react';

   function Example() {
       const [count, setCount] = useState(0);

       return (
           <div>
               <p>You clicked {count} times</p>
               <button onClick={() => setCount(count + 1)}>
                  Click me
               </button>
          </div>
        );
     }

Về cơ bản, chúng tôi sử dụng phương thức this.forceUpdate () để buộc thành phần hiển thị lại ngay lập tức trong các thành phần lớp React như ví dụ dưới đây

    class Test extends Component{
        constructor(props){
             super(props);
             this.state = {
                 count:0,
                 count2: 100
             }
             this.setCount = this.setCount.bind(this);//how can I do this with hooks in functional component 
        }
        setCount(){
              let count = this.state.count;
                   count = count+1;
              let count2 = this.state.count2;
                   count2 = count2+1;
              this.setState({count});
              this.forceUpdate();
              //before below setState the component will re-render immediately when this.forceUpdate() is called
              this.setState({count2: count
        }

        render(){
              return (<div>
                   <span>Count: {this.state.count}></span>. 
                   <button onClick={this.setCount}></button>
                 </div>
        }
 }

Nhưng truy vấn của tôi là Làm cách nào để buộc thành phần chức năng ở trên hiển thị lại ngay lập tức bằng hook?


1
Bạn có thể đăng một phiên bản của thành phần gốc của bạn sử dụng this.forceUpdate()? Có thể có một cách để đạt được điều tương tự mà không cần điều đó.
Jacob

Dòng cuối cùng trong setCount bị cắt bớt. Không rõ mục đích của setCount ở trạng thái hiện tại là gì.
Estus Flask

Đó chỉ là một hành động sau this.forceUpdate (); Tôi nói thêm rằng chỉ để giải thích về this.forceUpdate () trong câu hỏi của tôi
Hemadri Dasari

Đối với những gì nó đáng giá: Tôi đã vật lộn với điều này vì tôi nghĩ rằng tôi cần kết xuất lại thủ công và cuối cùng nhận ra rằng tôi chỉ cần di chuyển một biến được giữ bên ngoài vào một hook trạng thái và sử dụng chức năng cài đặt, điều này đã khắc phục tất cả các vấn đề của tôi mà không kết xuất lại. Không có nghĩa là nó không bao giờ cần thiết, nhưng bạn nên xem lại thứ ba và thứ tư để xem liệu nó có thực sự cần thiết trong trường hợp sử dụng cụ thể của bạn hay không.
Alexander Nied

Câu trả lời:


74

Điều này có thể thực hiện được với useStatehoặc useReducer, vì useStatesử dụng useReducernội bộ :

const [, updateState] = React.useState();
const forceUpdate = React.useCallback(() => updateState({}), []);

forceUpdatekhông được sử dụng trong các trường hợp bình thường, chỉ trong thử nghiệm hoặc các trường hợp nổi bật khác. Tình huống này có thể được giải quyết theo một cách thông thường hơn.

setCountlà một ví dụ về việc sử dụng không đúng cách forceUpdate, setStatekhông đồng bộ vì lý do hiệu suất và không nên bị buộc phải đồng bộ chỉ vì cập nhật trạng thái không được thực hiện chính xác. Nếu một trạng thái dựa vào trạng thái đã đặt trước đó, thì việc này sẽ được thực hiện với chức năng cập nhật ,

Nếu bạn cần đặt trạng thái dựa trên trạng thái trước đó, hãy đọc về đối số của trình cập nhật bên dưới.

<...>

Cả trạng thái và đạo cụ nhận được bởi chức năng trình cập nhật đều được đảm bảo là cập nhật. Đầu ra của trình cập nhật được hợp nhất nông với trạng thái.

setCount có thể không phải là một ví dụ minh họa vì mục đích của nó không rõ ràng nhưng đây là trường hợp của hàm cập nhật:

setCount(){
  this.setState(({count}) => ({ count: count + 1 }));
  this.setState(({count2}) => ({ count2: count + 1 }));
  this.setState(({count}) => ({ count2: count + 1 }));
}

Điều này được dịch 1: 1 thành hooks, ngoại trừ các hàm được sử dụng như callbacks tốt hơn nên được ghi nhớ:

   const [state, setState] = useState({ count: 0, count2: 100 });

   const setCount = useCallback(() => {
     setState(({count}) => ({ count: count + 1 }));
     setState(({count2}) => ({ count2: count + 1 }));
     setState(({count}) => ({ count2: count + 1 }));
   }, []);

Làm thế nào để const forceUpdate = useCallback(() => updateState({}), []);làm việc? Nó thậm chí còn buộc phải cập nhật?
Dávid Molnár

1
@ DávidMolnár ghi useCallbacknhớ forceUpdate, vì vậy nó luôn không đổi trong suốt tuổi thọ của thành phần và có thể được chuyển làm chỗ dựa một cách an toàn. updateState({})cập nhật trạng thái với đối tượng mới trên mỗi forceUpdatecuộc gọi, điều này dẫn đến kết xuất lại. Vì vậy, có, nó buộc cập nhật khi được gọi.
Estus Flask

3
Vì vậy, useCallbackphần không thực sự cần thiết. Nó sẽ hoạt động tốt mà không có nó.
Dávid Molnár

Và nó sẽ không hoạt động với updateState(0)0là một kiểu dữ liệu nguyên thủy? Nó phải là một đối tượng hay sẽ updateState([])(tức là sử dụng với một mảng) cũng hoạt động?
Andru

1
@Andru Có, trạng thái được cập nhật một lần vì 0 === 0. Có, một mảng sẽ hoạt động vì nó cũng là một đối tượng. Bất kỳ thứ gì không vượt qua kiểm tra bình đẳng đều có thể được sử dụng, như updateState(Math.random())hoặc một bộ đếm.
Estus Flask

26

Như những người khác đã đề cập, useStatehoạt động - đây là cách mobx-react-lite triển khai các bản cập nhật - bạn có thể làm điều gì đó tương tự.

Xác định một hook mới, useForceUpdate-

import { useState, useCallback } from 'react'

export function useForceUpdate() {
  const [, setTick] = useState(0);
  const update = useCallback(() => {
    setTick(tick => tick + 1);
  }, [])
  return update;
}

và sử dụng nó trong một thành phần -

const forceUpdate = useForceUpdate();
if (...) {
  forceUpdate(); // force re-render
}

Xem https://github.com/mobxjs/mobx-react-lite/blob/master/src/utils.tshttps://github.com/mobxjs/mobx-react-lite/blob/master/src/useObserver .ts


2
Theo những gì tôi hiểu từ hook, điều này có thể không hoạt động vì useForceUpdatesẽ trả về một hàm mới mỗi khi hàm hiển thị lại. Đối forceUpdatevới công việc khi được sử dụng trong một useEffect, nó sẽ trả về useCallback(update)Xem kentcdodds.com/blog/usememo-and-usecallback
Martin Ratinaud

Cảm ơn, @MartinRatinaud - vâng, nó có thể gây rò rỉ bộ nhớ nếu không sử dụngCallback (?) - đã được khắc phục.
Brian Burns

24

Nói chung, bạn có thể sử dụng bất kỳ phương pháp xử lý trạng thái nào mà bạn muốn để kích hoạt cập nhật.

Với TypeScript

ví dụ về hộp mã

useState

const forceUpdate: () => void = React.useState()[1].bind(null, {})  // see NOTE below

useReducer

const forceUpdate = React.useReducer(() => ({}), {})[1] as () => void

như móc tùy chỉnh

Chỉ cần bọc bất kỳ cách tiếp cận nào bạn thích như thế này

function useForceUpdate(): () => void {
  return React.useReducer(() => ({}), {})[1] as () => void // <- paste here
}

Làm thế nào điều này hoạt động?

" Để kích hoạt bản cập nhật " có nghĩa là thông báo cho React engine rằng một số giá trị đã thay đổi và nó sẽ hiển thị thành phần của bạn.

[, setState]từ useState()yêu cầu một tham số. Chúng tôi loại bỏ nó bằng cách ràng buộc một đối tượng mới {}.
() => ({})in useReducerlà một trình giảm thiểu giả trả về một đối tượng mới mỗi khi một hành động được gửi đi.
{} (đối tượng mới) là bắt buộc để nó kích hoạt cập nhật bằng cách thay đổi một tham chiếu trong trạng thái.

PS: useStatechỉ gói gọn useReducertrong nội bộ. nguồn

LƯU Ý: Sử dụng .bind với useState gây ra sự thay đổi trong tham chiếu hàm giữa các lần hiển thị. Có thể bọc nó bên trong useCallback như đã được giải thích ở đây , nhưng sau đó nó sẽ không phải là một lớp lót ™ sexy . Phiên bản Reducer đã giữ bình đẳng tham chiếu giữa các lần hiển thị. Điều này rất quan trọng nếu bạn muốn chuyển hàm forceUpdate trong đạo cụ.

JS đơn giản

const forceUpdate = React.useState()[1].bind(null, {})  // see NOTE above
const forceUpdate = React.useReducer(() => ({}))[1]

14

Thay thế cho câu trả lời của @ MinhKha:

Nó có thể sạch hơn nhiều với useReducer:

const [, forceUpdate] = useReducer(x => x + 1, 0);

Cách sử dụng: forceUpdate()- sạch hơn mà không có params


12

Bạn có thể chỉ cần xác định useState như vậy:

const [, forceUpdate] = React.useState(0);

Và cách sử dụng: forceUpdate(n => !n)

Hy vọng điều này giúp đỡ !


7
Sẽ không thành công nếu forceUpdate được gọi là số lần chẵn mỗi lần hiển thị.
Izhaki

Chỉ cần tiếp tục tăng giá trị.
Gary

2
Điều này dễ xảy ra lỗi và cần được xóa hoặc chỉnh sửa.
slikts

10

Tốt nhất bạn chỉ nên để thành phần của bạn phụ thuộc vào trạng thái và đạo cụ và nó sẽ hoạt động như mong đợi, nhưng nếu bạn thực sự cần một hàm để buộc thành phần hiển thị lại, bạn có thể sử dụng useStatehook và gọi hàm khi cần.

Thí dụ

const { useState, useEffect } = React;

function Foo() {
  const [, forceUpdate] = useState();

  useEffect(() => {
    setTimeout(forceUpdate, 2000);
  }, []);

  return <div>{Date.now()}</div>;
}

ReactDOM.render(<Foo />, document.getElementById("root"));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.production.min.js"></script>

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


Ok nhưng tại sao React lại giới thiệu this.forceUpdate (); ở vị trí đầu tiên khi thành phần hiển thị lại với setState trong các phiên bản trước?
Hemadri Dasari

1
@ Think-Twice Cá nhân tôi chưa bao giờ sử dụng nó và tôi không thể nghĩ ra trường hợp sử dụng tốt cho nó ngay bây giờ, nhưng tôi đoán đó là một lối thoát cho những trường hợp sử dụng thực sự đặc biệt. "Thông thường, bạn nên cố gắng tránh tất cả việc sử dụng forceUpdate()và chỉ đọc từ this.propsthis.statetrong render()."
Tholle

1
Đã đồng ý. Tôi thậm chí không bao giờ sử dụng mà theo kinh nghiệm của tôi, nhưng tôi biết làm thế nào nó hoạt động vì vậy chỉ cần cố gắng để hiểu làm thế nào điều tương tự có thể được thực hiện trong móc
Hemadri Dasari

1
@Tholle and I can't think of a good use case for it right now Tôi có một cái, nếu trạng thái không được kiểm soát bởi React thì sao. Tôi không sử dụng Redux, nhưng tôi cho rằng nó phải thực hiện một số loại buộc cập nhật. Cá nhân tôi sử dụng proxy để duy trì trạng thái, thành phần sau đó có thể kiểm tra các thay đổi của prop và sau đó cập nhật. Có vẻ hoạt động rất hiệu quả. Ví dụ: tất cả các thành phần của tôi sau đó được kiểm soát bởi một proxy, proxy này được hỗ trợ bởi SessionStorage, vì vậy ngay cả khi người dùng làm mới trang web của mình, trạng thái thả xuống thậm chí, v.v. vẫn được duy trì. IOW: Tôi hoàn toàn không sử dụng trạng thái, mọi thứ đều được điều khiển bằng đạo cụ.
Keith

9

Câu hỏi thường gặp về React Hooks giải pháp chính thức cho forceUpdate:

const [_, forceUpdate] = useReducer((x) => x + 1, 0);
// usage
<button onClick={forceUpdate}>Force update</button>

Ví dụ làm việc


7

Mã đơn giản

const forceUpdate = React.useReducer(bool => !bool)[1];

Sử dụng:

forceUpdate();

5

Bạn có thể (ab) sử dụng hook thông thường để buộc kết xuất bằng cách tận dụng thực tế là React không in boolean trong mã JSX

// create a hook
const [forceRerender, setForceRerender] = React.useState(true);

// ...put this line where you want to force a rerender
setForceRerender(!forceRerender);

// ...make sure that {forceRerender} is "visible" in your js code
// ({forceRerender} will not actually be visible since booleans are
// not printed, but updating its value will nonetheless force a
// rerender)
return (
  <div>{forceRerender}</div>
)


1
Trong trường hợp này, khi setBoolean thay đổi hai lần, React.useEffect có thể không bắt đầu lại bản cập nhật.
alma

Theo như tôi hiểu thì React coi mọi bản cập nhật boolean là lý do để kết xuất trang ngay cả khi boolean chuyển lại nhanh chóng. Điều đó nói rằng, React tất nhiên không phải là một tiêu chuẩn và chính xác cách nó hoạt động trong trường hợp này là không xác định và có thể thay đổi.
Fergie

1
Tôi không biết. Vì một số lý do mà tôi bị thu hút bởi phiên bản này. Nó làm tôi nhột :-). Bên cạnh đó, nó cảm thấy tinh khiết. Có gì đó đã thay đổi mà JSX của tôi phụ thuộc vào, vì vậy tôi kết xuất. Tính năng tàng hình không làm giảm đi IMO đó.
Adrian Bartholomew

4

Tùy chọn tiềm năng là chỉ buộc cập nhật trên thành phần cụ thể bằng cách sử dụng key. Việc cập nhật khóa kích hoạt hiển thị thành phần (không cập nhật được trước đó)

Ví dụ:

const [tableKey, setTableKey] = useState(1);
...

useEffect(() => {
    ...
    setTableKey(tableKey + 1);
}, [tableData]);

...
<DataTable
    key={tableKey}
    data={tableData}/>

1
Đây thường là cách rõ ràng nhất nếu có mối quan hệ 1: 1 giữa giá trị trạng thái và yêu cầu kết xuất lại.
jaimefps

1
đây là một giải pháp dễ dàng
Priyanka V


3

Giải pháp một dòng:

const useForceUpdate = () => useState()[1];

useState trả về một cặp giá trị: trạng thái hiện tại và một hàm cập nhật nó - trạng tháisetter , ở đây chúng tôi chỉ sử dụng setter để buộc kết xuất lại.


2

Biến thể của tôi forceUpdatekhông phải thông qua một countermà là thông qua một đối tượng:

// Emulates `forceUpdate()`
const [unusedState, setUnusedState] = useState()
const forceUpdate = useCallback(() => setUnusedState({}), [])

{} !== {}mọi lúc.


useCallback()gì? Thứ đó đã đến từ đâu? Giáo sư. Tôi thấy nó ngay bây giờ ...
zipzit

2

Điều này sẽ hiển thị các thành phần phụ thuộc 3 lần (các mảng có các phần tử bằng nhau không bằng nhau):

const [msg, setMsg] = useState([""])

setMsg(["test"])
setMsg(["test"])
setMsg(["test"])

1
Tôi tin rằng bạn thậm chí không cần phải đặt một mục trong mảng. Một mảng trống không hoàn toàn bằng một mảng trống khác chỉ đơn giản bằng tham chiếu khác nhau, giống như các đối tượng.
Qwerty

vâng, chỉ muốn thể hiện điều đó như một cách truyền dữ liệu
Janek Olszak

1

Đối với các thành phần dựa trên Lớp React thông thường, hãy tham khảo Tài liệu React cho forceUpdateapi tại URL này . Tài liệu đề cập rằng:

Thông thường, bạn nên cố gắng tránh tất cả việc sử dụng forceUpdate () và chỉ đọc từ this.props và this.state trong render ()

Tuy nhiên, nó cũng được đề cập trong tài liệu rằng:

Nếu phương thức render () của bạn phụ thuộc vào một số dữ liệu khác, bạn có thể cho React biết rằng thành phần cần kết xuất lại bằng cách gọi forceUpdate ().

Vì vậy, mặc dù các trường hợp sử dụng để sử dụng forceUpdatecó thể hiếm và tôi chưa sử dụng nó bao giờ, tuy nhiên tôi đã thấy nó được các nhà phát triển khác sử dụng trong một số dự án công ty cũ mà tôi đã làm việc.

Vì vậy, để biết chức năng tương đương cho các thành phần chức năng, hãy tham khảo Tài liệu phản ứng cho HOOKS tại URL này . Theo URL ở trên, người ta có thể sử dụng hook "useReducer" để cung cấp forceUpdatechức năng cho các thành phần chức năng.

Mẫu mã làm việc that does not use state or propsđược cung cấp bên dưới, cũng có sẵn trên CodeSandbox tại URL này

import React, { useReducer, useRef } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function App() {
  // Use the useRef hook to store a mutable value inside a functional component for the counter
  let countref = useRef(0);

  const [, forceUpdate] = useReducer(x => x + 1, 0);

  function handleClick() {
    countref.current++;
    console.log("Count = ", countref.current);
    forceUpdate(); // If you comment this out, the date and count in the screen will not be updated
  }

  return (
    <div className="App">
      <h1> {new Date().toLocaleString()} </h1>
      <h2>You clicked {countref.current} times</h2>
      <button
        onClick={() => {
          handleClick();
        }}
      >
        ClickToUpdateDateAndCount
      </button>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

LƯU Ý: Một cách tiếp cận thay thế bằng hook useState (thay vì useReducer) cũng có sẵn tại URL này .


1

react-tidycó một móc tùy chỉnh chỉ để làm điều đó được gọi là useRefresh:

import React from 'react'
import {useRefresh} from 'react-tidy'

function App() {
  const refresh = useRefresh()
  return (
    <p>
      The time is {new Date()} <button onClick={refresh}>Refresh</button>
    </p>
  )
}

Tìm hiểu thêm về móc này

Tuyên bố từ chối trách nhiệm Tôi là người viết thư viện này.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.