ฉันสามารถตั้งค่าสถานะภายในตะขอ useEffect ได้หรือไม่


124

สมมติว่าฉันมีสถานะบางอย่างที่ขึ้นอยู่กับสถานะอื่น (เช่นเมื่อ A เปลี่ยนแปลงฉันต้องการให้ B เปลี่ยน)

เหมาะสมหรือไม่ที่จะสร้างตะขอที่สังเกตเห็น A และตั้งค่า B ไว้ในตะขอ useEffect

เอฟเฟกต์จะเรียงซ้อนเช่นนั้นหรือไม่เมื่อฉันคลิกปุ่มเอฟเฟกต์แรกจะเริ่มทำงานทำให้ b เปลี่ยนไปทำให้เอฟเฟกต์ที่สองเริ่มทำงานก่อนการเรนเดอร์ถัดไป มีข้อเสียด้านประสิทธิภาพในการจัดโครงสร้างโค้ดเช่นนี้หรือไม่?

let MyComponent = props => {
  let [a, setA] = useState(1)
  let [b, setB] = useState(2)
  useEffect(
    () => {
      if (/*some stuff is true*/) {
        setB(3)
      }
    },
    [a],
  )
  useEffect(
    () => {
      // do some stuff
    },
    [b],
  )

  return (
    <button
      onClick={() => {
        setA(5)
      }}
    >
      click me
    </button>
  )
}

คำตอบ:


31

เอฟเฟกต์จะถูกดำเนินการเสมอหลังจากเฟสการเรนเดอร์เสร็จสิ้นแม้ว่าคุณ setState ภายในเอฟเฟกต์เดียวเอฟเฟกต์อื่นจะอ่านสถานะที่อัปเดตและดำเนินการกับมันหลังจากเฟสเรนเดอร์เท่านั้น

ต้องบอกว่ามันน่าจะดีกว่าที่จะดำเนินการทั้งสองอย่างให้มีผลเหมือนกันเว้นแต่จะมีความเป็นไปได้ที่bสามารถเปลี่ยนแปลงได้เนื่องจากเหตุผลอื่นนอกเหนือจากนั้นchanging aในกรณีนี้คุณก็ต้องการใช้ตรรกะเดียวกัน


4
ดังนั้นถ้า A เปลี่ยน B ส่วนประกอบจะแสดงผลสองครั้งใช่ไหม
Dan Ruswick

2
ฉันต้องการทราบคำตอบสำหรับคำถามข้างต้นด้วย
alaboudi

2
@alaboudi ใช่ถ้าการเปลี่ยนแปลงที่ทำให้เกิดประโยชน์ในการรันซึ่งตั้งค่า B ส่วนประกอบจะแสดงผลสองครั้ง
Shubham Khatri

@alaboudi ใช่ .. ตามที่ Shubham Khatri กล่าวว่าจะแสดงอีกครั้ง แต่คุณสามารถข้ามการเรียกเอฟเฟกต์ของคุณได้หลังจากการเรนเดอร์โดยใช้อาร์กิวเมนต์ที่สองอ้างถึงreactjs.org/docs/…
Karthikeyan

108

โดยทั่วไปการใช้setStateภายในuseEffectจะสร้างลูปที่ไม่มีที่สิ้นสุดซึ่งส่วนใหญ่คุณไม่ต้องการให้เกิด มีข้อยกเว้นบางประการสำหรับกฎนั้นซึ่งฉันจะเข้าสู่ในภายหลัง

useEffectถูกเรียกหลังจากการเรนเดอร์แต่ละครั้งและเมื่อsetStateถูกใช้ภายในมันจะทำให้คอมโพเนนต์แสดงผลอีกครั้งซึ่งจะเรียกuseEffectและอื่น ๆ ไปเรื่อย ๆ

หนึ่งในกรณียอดนิยมที่ใช้useStateinside of useEffectจะไม่ทำให้เกิดการวนซ้ำแบบไม่มีที่สิ้นสุดคือเมื่อคุณส่งอาร์เรย์ว่างเป็นอาร์กิวเมนต์ที่สองเพื่อuseEffectชอบuseEffect(() => {....}, [])ซึ่งหมายความว่าควรเรียกใช้ฟังก์ชันเอฟเฟกต์ครั้งเดียว: หลังจากเมาท์ / เรนเดอร์แรกเท่านั้น ใช้กันอย่างแพร่หลายเมื่อคุณกำลังดึงข้อมูลในองค์ประกอบและคุณต้องการบันทึกข้อมูลคำขอในสถานะของคอมโพเนนต์


3
อีกกรณีหนึ่งที่จะใช้setStateภายในuseEffectคือsetting stateการสมัครสมาชิกหรือผู้ฟังเหตุการณ์ แต่อย่าลืมยกเลิก reactjs.org/docs/hooks-effect.html#effects-with-cleanup
timqian

35
สิ่งนี้ไม่เป็นความจริงอย่างเคร่งครัด - useState จะเริ่มทำงานก็ต่อเมื่อค่าที่คุณกำลังอัปเดตสถานะแตกต่างจากค่าก่อนหน้าดังนั้นการวนซ้ำแบบไม่มีที่สิ้นสุดจะถูกป้องกันเว้นแต่ค่าจะเปลี่ยนไประหว่างรอบ
RobM

14
คำตอบนี้ไม่ถูกต้องและไม่ตรงประเด็น: ในโค้ดในกรณีส่วนประกอบจะแสดงผลเพียงสองครั้งเมื่อคลิกปุ่มไม่มีการวนซ้ำไม่สิ้นสุด
Bogdan D

15
เป็นกรณีการใช้งานที่พบบ่อยมากในการตั้งค่าสถานะภายใน useEffect คิดเกี่ยวกับการโหลดข้อมูล useEffect เรียก API รับข้อมูลชุดโดยใช้ set part of useState
badbod99

3
ฉันอัปเดตคำตอบเพื่อแก้ไขปัญหาที่คุณกล่าวถึงตอนนี้ดีกว่าไหม
Hossam Mourad

58

สำหรับวัตถุประสงค์ในอนาคตสิ่งนี้อาจช่วยได้เช่นกัน:

เป็นเรื่องปกติที่จะใช้ setState ในตัวuseEffectคุณเพียงแค่ต้องให้ความสนใจตามที่อธิบายไว้แล้วเพื่อไม่สร้างลูป

แต่ไม่ใช่ปัญหาเดียวที่อาจเกิดขึ้น ดูด้านล่าง:

ลองนึกภาพว่าคุณมีส่วนประกอบCompที่ได้รับpropsจากผู้ปกครองและตามการpropsเปลี่ยนแปลงที่คุณต้องการตั้งค่าCompสถานะ ด้วยเหตุผลบางประการคุณต้องเปลี่ยนสำหรับแต่ละเสาในรูปแบบที่แตกต่างกันuseEffect:

อย่าทำอย่างนี้

useEffect(() => {
  setState({ ...state, a: props.a });
}, [props.a]);

useEffect(() => {
  setState({ ...state, b: props.b });
}, [props.b]);

มันอาจไม่เปลี่ยนสถานะของ a อย่างที่คุณเห็นในตัวอย่างนี้: https://codesandbox.io/s/confident-lederberg-dtx7w

เหตุผลที่ว่าทำไมเรื่องนี้เกิดขึ้นในตัวอย่างนี้ก็เพราะทั้ง useEffects ทำงานในเดียวกันตอบสนองวงจรเมื่อคุณเปลี่ยนทั้งสองprop.aและprop.bเพื่อให้ค่าของ{...state}เมื่อคุณไม่setStateอยู่เหมือนกันทั้งในuseEffectเพราะพวกเขาอยู่ในบริบทเดียวกัน เมื่อคุณเรียกใช้ที่สองก็จะเข้ามาแทนที่คนแรกsetStatesetState

ทำสิ่งนี้แทน

วิธีแก้ปัญหานี้โดยทั่วไปเรียกsetStateแบบนี้:

useEffect(() => {
  setState(state => ({ ...state, a: props.a }));
}, [props.a]);

useEffect(() => {
  setState(state => ({ ...state, b: props.b }));
}, [props.b]);

ตรวจสอบวิธีแก้ปัญหาที่นี่: https://codesandbox.io/s/mutable-surf-nynlx

ตอนนี้คุณจะได้รับค่าสถานะที่อัปเดตและถูกต้องที่สุดเสมอเมื่อคุณดำเนินการกับไฟล์setState.

ฉันหวังว่านี่จะช่วยใครสักคน!


3
วิธีแก้ปัญหาข้างต้นช่วยฉันsetName(name => ({ ...name, a: props.a }));
mufaddal_mw

2
สิ่งนี้ช่วยฉันได้เช่นกันในส่วนของฟังก์ชันลูกศรsetItems(items => [...items, item])
Stefan Zhelyazkov

ว้าวคุณยอดเยี่ยมมาก คุณช่วยชีวิตของฉันวันนี้
Pratik Soni

ใช้เวลา 7.00 น. ถึง 15.00 น. โดยไม่มีทางออกและตอนนี้คุณช่วยฉันแล้ว
Dinindu Kanchana

24

useEffectสามารถขอเกี่ยวกับเสาหรือสถานะบางอย่างได้ ดังนั้นสิ่งที่คุณต้องทำเพื่อหลีกเลี่ยงการวนซ้ำแบบไม่มีที่สิ้นสุดคือการผูกตัวแปรหรือสถานะให้มีผล

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

useEffect(myeffectCallback, [])

เอฟเฟกต์ข้างต้นจะเริ่มทำงานเมื่อมีการแสดงผลส่วนประกอบเท่านั้น สิ่งนี้คล้ายกับcomponentDidMountวงจรชีวิต

const [something, setSomething] = withState(0)
const [myState, setMyState] = withState(0)
useEffect(() => {
  setSomething(0)
}, myState)

เอฟเฟกต์ข้างต้นจะเริ่มทำงานเฉพาะสถานะของฉันเท่านั้นที่เปลี่ยนแปลงสิ่งนี้คล้ายกับcomponentDidUpdateยกเว้นสถานะที่เปลี่ยนแปลงทุกครั้งจะเริ่มทำงาน

คุณสามารถอ่านรายละเอียดเพิ่มเติมได้จากลิงค์นี้


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

11

▶ 1. ฉันสามารถตั้งค่าสถานะภายในตะขอ useEffect ได้หรือไม่?

โดยหลักการแล้วคุณสามารถตั้งค่าสถานะได้อย่างอิสระในที่ที่คุณต้องการ - รวมถึงภายในuseEffectและแม้กระทั่งในระหว่างการเรนเดอร์ ตรวจสอบให้แน่ใจว่าได้หลีกเลี่ยงการวนซ้ำที่ไม่มีที่สิ้นสุดโดยการตั้งค่า Hook depsอย่างถูกต้องและ / หรือสถานะตามเงื่อนไข


▶ 2. ให้บอกว่าฉันมีสถานะบางอย่างที่ขึ้นอยู่กับรัฐอื่น เหมาะสมหรือไม่ที่จะสร้างตะขอที่สังเกต A และกำหนด B ไว้ในตะขอ useEffect

คุณเพียงแค่อธิบายกรณีการใช้งานคลาสสิกสำหรับ:useReducer

useReducerโดยปกติจะนิยมใช้useStateเมื่อคุณมีลอจิกสถานะที่ซับซ้อนซึ่งเกี่ยวข้องกับค่าย่อยหลายค่าหรือเมื่อสถานะถัดไปขึ้นอยู่กับค่าก่อนหน้า ( ตอบสนองเอกสาร )

เมื่อตั้งค่าสถานะตัวแปรขึ้นอยู่กับมูลค่าปัจจุบันของอีกรัฐหนึ่งuseReducerตัวแปรที่คุณอาจต้องการที่จะลองเปลี่ยนพวกเขาทั้งสองด้วย [... ] เมื่อคุณพบว่าตัวเองกำลังเขียน setSomething(something => ...)ถึงเวลาที่ควรพิจารณาใช้ตัวลดแทน ( Dan Abramov บล็อกที่ตอบสนองมากเกินไป )


▶ 3. เอฟเฟกต์จะเรียงซ้อนเช่นนั้นหรือไม่เมื่อฉันคลิกปุ่มเอฟเฟกต์แรกจะเริ่มทำงานทำให้ b เปลี่ยนไปทำให้เอฟเฟกต์ที่สองเริ่มทำงานก่อนการเรนเดอร์ถัดไป?

useEffectทำงานหลังจากที่มีการคอมมิตการแสดงผลเสมอและจะใช้การเปลี่ยนแปลง DOM เอฟเฟกต์แรกเริ่มทำงานเปลี่ยนแปลงbและทำให้เกิดการแสดงผลซ้ำ หลังจากการเรนเดอร์เสร็จสมบูรณ์เอฟเฟกต์ที่สองจะทำงานเนื่องจากการbเปลี่ยนแปลง


▶ 4. มีข้อเสียด้านประสิทธิภาพในการจัดโครงสร้างโค้ดเช่นนี้หรือไม่?

ใช่. ด้วยการตัดการเปลี่ยนแปลงสถานะbในแยกต่างหากuseEffectสำหรับaเบราว์เซอร์จะมีขั้นตอนการจัดวาง / ทาสีเพิ่มเติมซึ่งผู้ใช้อาจมองเห็นผลกระทบเหล่านี้ได้ หากไม่มีวิธีที่คุณต้องการuseReducerลองคุณสามารถเปลี่ยนbสถานะร่วมกับa:

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