ฉันจะบังคับให้คอมโพเนนต์แสดงผลใหม่โดยใช้ hooks ใน React ได้อย่างไร


116

พิจารณาตัวอย่างตะขอด้านล่าง

   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>
        );
     }

โดยทั่วไปเราใช้เมธอด this.forceUpdate () เพื่อบังคับให้คอมโพเนนต์แสดงผลซ้ำทันทีในคอมโพเนนต์คลาส React ดังตัวอย่างด้านล่าง

    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>
        }
 }

แต่คำถามของฉันคือฉันจะบังคับให้องค์ประกอบการทำงานด้านบนแสดงผลใหม่ทันทีด้วย hooks ได้อย่างไร


1
คุณสามารถโพสต์เวอร์ชันของส่วนประกอบดั้งเดิมของคุณที่ใช้this.forceUpdate()? อาจมีวิธีที่จะทำสิ่งเดียวกันให้สำเร็จโดยไม่ต้องทำเช่นนั้น
Jacob

บรรทัดสุดท้ายใน setCount ถูกตัดทอน ไม่ชัดเจนว่าจุดประสงค์ของ setCount ในสถานะปัจจุบันคืออะไร
Estus Flask

นั่นเป็นเพียงการกระทำหลังจากนี้forceUpdate (); ฉันเสริมว่าเพื่ออธิบายเกี่ยวกับเรื่องนี้forceUpdate () ในคำถามของฉัน
Hemadri Dasari

สำหรับสิ่งที่คุ้มค่า: ฉันต่อสู้กับสิ่งนี้เพราะฉันคิดว่าฉันต้องการการเรนเดอร์ซ้ำด้วยตนเองและในที่สุดก็ตระหนักว่าฉันเพียงแค่ต้องย้ายตัวแปรที่ถือไว้ภายนอกไปยังตะขอสถานะและใช้ประโยชน์จากฟังก์ชันการตั้งค่าซึ่งแก้ไขปัญหาทั้งหมดของฉันโดยไม่ต้อง re-render ไม่ได้บอกว่าไม่จำเป็น แต่ควรลองดูครั้งที่สามและสี่เพื่อดูว่าจำเป็นจริงหรือไม่ในกรณีการใช้งานเฉพาะของคุณ
Alexander Nied

คำตอบ:


78

สิ่งนี้เป็นไปได้โดยมีuseStateหรือuseReducerเนื่องจากuseStateใช้useReducerภายใน :

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

forceUpdateไม่ได้มีวัตถุประสงค์เพื่อใช้ในสถานการณ์ปกติเฉพาะในการทดสอบหรือกรณีอื่น ๆ ที่โดดเด่น สถานการณ์นี้อาจได้รับการแก้ไขด้วยวิธีที่ธรรมดากว่า

setCountเป็นตัวอย่างของการไม่ถูกต้องที่ใช้forceUpdate, setStateไม่ตรงกันเพื่อเหตุผลด้านประสิทธิภาพและไม่ควรบังคับให้เป็นซิงโครการปรับปรุงเพียงเพราะรัฐไม่ได้ดำเนินการอย่างถูกต้อง หากสถานะอาศัยสถานะที่ตั้งไว้ก่อนหน้านี้ควรทำด้วยฟังก์ชันตัวอัปเดต

หากคุณต้องการตั้งค่าสถานะตามสถานะก่อนหน้าโปรดอ่านเกี่ยวกับอาร์กิวเมนต์ตัวอัปเดตด้านล่าง

<... >

ทั้งสถานะและอุปกรณ์ประกอบฉากที่ได้รับจากฟังก์ชันตัวอัปเดตจะรับประกันได้ว่าเป็นข้อมูลล่าสุด เอาต์พุตของตัวอัปเดตถูกผสานเข้ากับสถานะอย่างตื้น ๆ

setCount อาจไม่ใช่ตัวอย่างประกอบเนื่องจากจุดประสงค์ไม่ชัดเจน แต่เป็นกรณีของฟังก์ชันตัวอัปเดต:

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

สิ่งนี้ได้รับการแปล 1: 1 เป็น hooks โดยมีข้อยกเว้นว่าฟังก์ชันที่ใช้เป็น callbacks ควรได้รับการบันทึกไว้ดีกว่า:

   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 }));
   }, []);

วิธีการconst forceUpdate = useCallback(() => updateState({}), []);ทำงานหรือไม่ มันบังคับให้อัพเดตหรือไม่?
DávidMolnár

2
@ DávidMolnárช่วยuseCallbackจำforceUpdateดังนั้นมันจึงคงที่ตลอดอายุการใช้งานของส่วนประกอบและสามารถส่งผ่านเป็นเสาได้อย่างปลอดภัย updateState({})อัปเดตสถานะด้วยออบเจ็กต์ใหม่ในแต่ละการforceUpdateโทรซึ่งส่งผลให้เกิดการแสดงผล ใช่มันบังคับให้อัปเดตเมื่อถูกเรียก
Estus Flask

3
ดังนั้นuseCallbackส่วนที่ไม่จำเป็นจริงๆ มันควรจะทำงานได้ดีถ้าไม่มีมัน
DávidMolnár

และจะใช้ไม่ได้กับupdateState(0)เพราะ0เป็นชนิดข้อมูลดั้งเดิม? มันต้องเป็นวัตถุหรือไม่หรือupdateState([])(เช่นการใช้งานกับอาร์เรย์)
Andru

1
@Andru ใช่สถานะจะถูกอัปเดตหนึ่งครั้งเนื่องจาก 0 === 0 ใช่อาร์เรย์จะใช้งานได้เนื่องจากเป็นวัตถุด้วย สิ่งใดก็ตามที่ไม่ผ่านการตรวจสอบความเท่าเทียมกันสามารถใช้เช่นupdateState(Math.random())หรือตัวนับ
Estus Flask

29

ดังที่คนอื่น ๆ กล่าวถึงการuseStateทำงาน - นี่คือวิธีที่mobx-react-liteดำเนินการอัปเดตคุณสามารถทำสิ่งที่คล้ายกันได้

กำหนดเบ็ดใหม่useForceUpdate-

import { useState, useCallback } from 'react'

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

และใช้เป็นส่วนประกอบ -

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

ดูhttps://github.com/mobxjs/mobx-react-lite/blob/master/src/utils.tsและhttps://github.com/mobxjs/mobx-react-lite/blob/master/src/useObserver .ts


2
จากสิ่งที่ฉันเข้าใจจาก hooks สิ่งนี้อาจไม่ได้ผลเนื่องจากuseForceUpdateจะส่งคืนฟังก์ชันใหม่ทุกครั้งที่ฟังก์ชันแสดงผลซ้ำ สำหรับforceUpdateการทำงานเมื่อใช้ใน a useEffectควรส่งคืนuseCallback(update)ดูkentcdodds.com/blog/usememo-and-usecallback
Martin Ratinaud

ขอบคุณ @MartinRatinaud - ใช่มันอาจทำให้หน่วยความจำรั่วโดยไม่ใช้ useCallback (?) - ได้รับการแก้ไข
Brian Burns

27

โดยทั่วไปคุณสามารถใช้วิธีการจัดการสถานะใดก็ได้ที่คุณต้องการเรียกใช้การอัปเดต

ด้วย TypeScript

ตัวอย่างรหัสแซนด์บ็อกซ์

useState

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

useReducer

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

เป็นตะขอที่กำหนดเอง

เพียงห่อแนวทางที่คุณต้องการเช่นนี้

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

วิธีนี้ทำงานอย่างไร

" เพื่อทริกเกอร์การอัปเดต "หมายถึงการบอกให้ React engine ทราบว่าค่าบางอย่างมีการเปลี่ยนแปลงและควรแสดงผลส่วนประกอบของคุณ

[, setState]จากuseState()ต้องการพารามิเตอร์ {}เรากำจัดมันโดยการจับวัตถุที่สดใหม่
() => ({})in useReducerเป็นตัวลดดัมมี่ที่ส่งคืนวัตถุใหม่ทุกครั้งที่มีการส่งการดำเนินการ
{} (วัตถุสด)เป็นสิ่งจำเป็นเพื่อให้ทริกเกอร์การอัปเดตโดยเปลี่ยนการอ้างอิงในสถานะ

PS: useStateเพียงแค่ห่อuseReducerภายใน แหล่งที่มา

หมายเหตุ:การใช้. ผูกกับ useState ทำให้เกิดการเปลี่ยนแปลงในการอ้างอิงฟังก์ชันระหว่างการแสดงผล มันเป็นไปได้ที่จะห่อมันไว้ภายใน useCallback เป็นแล้วอธิบายที่นี่แต่แล้วก็จะไม่เป็นเซ็กซี่หนึ่งซับ™ รุ่น Reducerรักษาความเท่าเทียมกันในการอ้างอิงระหว่างการแสดงผลอยู่แล้ว นี่เป็นสิ่งสำคัญหากคุณต้องการส่งผ่านฟังก์ชัน forceUpdate ในอุปกรณ์ประกอบฉาก

JS ธรรมดา

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

17

ทางเลือกสำหรับคำตอบของ @ MinhKha:

สามารถทำความสะอาดได้มากขึ้นด้วยuseReducer:

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

การใช้งาน: forceUpdate()- ทำความสะอาดโดยไม่ต้องใช้ params


13

คุณสามารถกำหนด useState ได้ดังนี้:

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

และการใช้งาน: forceUpdate(n => !n)

หวังว่านี่จะช่วยได้!


7
จะล้มเหลวหากมีการเรียกใช้ ForceUpdate จำนวนครั้งต่อการแสดงผล
Izhaki

เพียงแค่เพิ่มมูลค่า
Gary

2
เกิดข้อผิดพลาดได้ง่ายและควรลบหรือแก้ไข
slikts

11

React Hooks FAQวิธีแก้ปัญหาอย่างเป็นทางการสำหรับforceUpdate:

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

ตัวอย่างการทำงาน


10

คุณควรกำหนดให้ส่วนประกอบของคุณขึ้นอยู่กับสถานะและอุปกรณ์ประกอบฉากเท่านั้นและจะทำงานได้ตามที่คาดไว้ แต่ถ้าคุณต้องการฟังก์ชันเพื่อบังคับให้คอมโพเนนต์แสดงผลใหม่คุณสามารถใช้useStatehook และเรียกใช้ฟังก์ชันเมื่อจำเป็น

ตัวอย่าง

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>


ตกลง แต่ทำไม React ถึงแนะนำ this.forceUpdate (); ในตอนแรกเมื่อคอมโพเนนต์แสดงผลด้วย setState ในเวอร์ชันก่อนหน้า?
Hemadri Dasari

1
@ Think-Twice โดยส่วนตัวแล้วฉันไม่เคยใช้มันเลยและตอนนี้ฉันไม่สามารถนึกถึงกรณีการใช้งานที่ดีได้ แต่ฉันคิดว่ามันเป็นช่องทางหลบหนีสำหรับกรณีการใช้งานพิเศษเหล่านั้นจริงๆ "โดยปกติคุณควรพยายามหลีกเลี่ยงการใช้งานทั้งหมดforceUpdate()และอ่านจากthis.propsและthis.stateในrender()เท่านั้น"
Tholle

1
ตกลง ฉันไม่เคยใช้มันจากประสบการณ์ของฉัน แต่ฉันรู้ว่ามันทำงานอย่างไรดังนั้นแค่พยายามทำความเข้าใจว่าสิ่งเดียวกันนี้สามารถทำได้อย่างไรในตะขอ
Hemadri Dasari

1
@Tholle and I can't think of a good use case for it right now ฉันมีอยู่แล้วถ้าสถานะไม่ได้รับการควบคุมโดย React ฉันไม่ได้ใช้ Redux แต่ฉันคิดว่ามันต้องมีการบังคับอัปเดตบางอย่าง ฉันใช้พร็อกซีเป็นการส่วนตัวเพื่อรักษาสถานะจากนั้นคอมโพเนนต์สามารถตรวจสอบการเปลี่ยนแปลง prop จากนั้นอัปเดต ดูเหมือนว่าจะทำงานได้อย่างมีประสิทธิภาพมากเกินไป เช่นส่วนประกอบทั้งหมดของฉันถูกควบคุมโดยพร็อกซีแล้วพร็อกซีนี้ได้รับการสนับสนุนโดย SessionStorage ดังนั้นแม้ว่าผู้ใช้จะรีเฟรชหน้าเว็บของเขาสถานะของแม้แต่ดร็อปดาวน์ ฯลฯ จะยังคงอยู่ IOW: ฉันไม่ได้ใช้สถานะเลยทุกอย่างควบคุมด้วยอุปกรณ์ประกอบฉาก
Keith


5

คุณสามารถ (ab) ใช้ hooks ปกติเพื่อบังคับให้ rerender โดยใช้ประโยชน์จากข้อเท็จจริงที่ว่าReact ไม่พิมพ์บูลีนในโค้ด 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
ในกรณีนี้เมื่อ setBoolean เปลี่ยนแปลงลูกสองครั้ง React.useEffect อาจไม่เข้าสู่การอัปเดตอีกครั้ง
alma

เท่าที่ฉันเข้าใจ React ถือว่าการอัปเดตบูลีนทุกครั้งเป็นเหตุผลในการแสดงผลหน้าแม้ว่าบูลีนจะเปลี่ยนกลับมาอีกครั้งอย่างรวดเร็ว ที่กล่าวว่า React ไม่ใช่มาตรฐานและวิธีการทำงานในอินสแตนซ์นี้ไม่ได้กำหนดไว้และอาจมีการเปลี่ยนแปลงได้
Fergie

1
ฉันไม่รู้ ด้วยเหตุผลบางประการฉันจึงติดใจเวอร์ชันนี้ มันจั๊กจี้ฉัน :-) นอกจากนี้ยังรู้สึกบริสุทธิ์ มีบางอย่างเปลี่ยนแปลงไปที่ JSX ของฉันขึ้นอยู่กับดังนั้นฉันจึงแสดงผล การมองไม่เห็นไม่ได้เบี่ยงเบนจาก IMO นั้น
Adrian Bartholomew

4

keyตัวเลือกที่มีศักยภาพคือการปรับปรุงการบังคับใช้เฉพาะในองค์ประกอบที่เฉพาะเจาะจงโดยใช้ การอัปเดตคีย์ทำให้เกิดการแสดงผลของส่วนประกอบ (ซึ่งอัปเดตไม่สำเร็จก่อนหน้านี้)

ตัวอย่างเช่น:

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

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

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

1
วิธีนี้มักจะเป็นวิธีที่สะอาดที่สุดหากมีความสัมพันธ์แบบ 1: 1 ระหว่างค่าสถานะและข้อกำหนดในการแสดงผลอีกครั้ง
jaimefps

1
นี่เป็นทางออกที่ง่าย
Priyanka V


3

โซลูชันหนึ่งบรรทัด:

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

useStateส่งคืนค่าคู่: สถานะปัจจุบันและฟังก์ชันที่อัปเดต - สถานะและตัวตั้งค่าที่นี่เราใช้เฉพาะ setter เพื่อบังคับให้ re-render


2

รูปแบบของฉันforceUpdateไม่ได้ผ่านทาง a counterแต่ผ่านทางวัตถุ:

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

เพราะ{} !== {}ทุกครั้ง.


คืออะไรuseCallback()? ไหนว่ามาจากไหน? โอ๊ะโอ เห็นแล้ว ...
zipzit

2

สิ่งนี้จะแสดงผลขึ้นอยู่กับส่วนประกอบ 3 ครั้ง (อาร์เรย์ที่มีองค์ประกอบเท่ากันไม่เท่ากัน):

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

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

1
ฉันเชื่อว่าคุณไม่จำเป็นต้องใส่รายการในอาร์เรย์ด้วยซ้ำ อาร์เรย์ว่างไม่ได้เท่ากับอาร์เรย์ว่างอื่น ๆ โดยการอ้างอิงที่แตกต่างกันเช่นเดียวกับวัตถุ
Qwerty

ใช่แค่ต้องการแสดงให้เห็นว่าเป็นวิธีการส่งผ่านข้อมูล
Janek Olszak

2

react-tidyมีเบ็ดที่กำหนดเองสำหรับการทำสิ่งที่เรียกว่า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>
  )
}

เรียนรู้เพิ่มเติมเกี่ยวกับเบ็ดนี้

ข้อจำกัดความรับผิดชอบฉันเป็นผู้เขียนห้องสมุดนี้


1

สำหรับการตอบสนองปกติชั้นส่วนประกอบตามอ้างถึงตอบสนองเอกสารสำหรับforceUpdateAPI ที่นี้ URL เอกสารกล่าวถึงว่า:

โดยปกติคุณควรพยายามหลีกเลี่ยงการใช้ forceUpdate () ทั้งหมดและอ่านจาก this.props และ this.state ใน render () เท่านั้น

อย่างไรก็ตามมีการกล่าวถึงในเอกสารด้วยว่า:

หากเมธอด render () ของคุณขึ้นอยู่กับข้อมูลอื่นคุณสามารถบอก React ได้ว่าคอมโพเนนต์นั้นต้องการการเรนเดอร์โดยเรียกใช้ forceUpdate ()

ดังนั้นแม้ว่ากรณีการใช้งานสำหรับการใช้งานforceUpdateอาจจะหายากและฉันก็ไม่เคยใช้เลย แต่ฉันเคยเห็นมันใช้โดยนักพัฒนารายอื่นในโครงการขององค์กรแบบเดิมที่ฉันเคยทำ

ดังนั้นสำหรับการทำงานที่เทียบเท่าสำหรับฟังก์ชั่นคอมโพเนนต์อ้างถึงตอบสนองเอกสารสำหรับเบ็ดที่นี้ URL ตาม URL ด้านบนเราสามารถใช้ตะขอ "useReducer" เพื่อระบุไฟล์forceUpdateฟังก์ชันการทำงานสำหรับส่วนประกอบของฟังก์ชัน

ตัวอย่างโค้ดการทำงานthat does not use state or propsอยู่ด้านล่างซึ่งมีอยู่ใน CodeSandbox ที่ URL นี้

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);

หมายเหตุ: วิธีอื่นโดยใช้ useState hook (แทน useReducer) มีอยู่ในURL นี้เช่นกัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.