ค้นหาเว็บไซต์

javascript - React Hooks ใช้ useState() กับวัตถุ


วิธีที่ถูกต้องในการอัปเดต state ในอ็อบเจ็กต์ซ้อนกันใน React ด้วย Hooks คืออะไร?

export Example = () => {
  const [exampleState, setExampleState] = useState(
  {masterField: {
        fieldOne: "a",
        fieldTwo: {
           fieldTwoOne: "b"
           fieldTwoTwo: "c"
           }
        }
   })

จะใช้ setExampleState เพื่ออัปเดต exampleState เป็น a (เพิ่มฟิลด์) อย่างไร?

const a = {
masterField: {
        fieldOne: "a",
        fieldTwo: {
           fieldTwoOne: "b",
           fieldTwoTwo: "c"
           }
        },
  masterField2: {
        fieldOne: "c",
        fieldTwo: {
           fieldTwoOne: "d",
           fieldTwoTwo: "e"
           }
        },
   }
}

b (กำลังเปลี่ยนค่า)?

const b = {masterField: {
        fieldOne: "e",
        fieldTwo: {
           fieldTwoOne: "f"
           fieldTwoTwo: "g"
           }
        }
   })

คำตอบ #1

คุณสามารถส่งค่าใหม่ได้แบบนี้:

  setExampleState({...exampleState,  masterField2: {
        fieldOne: "a",
        fieldTwo: {
           fieldTwoOne: "b",
           fieldTwoTwo: "c"
           }
        },
   })

คำตอบ #2

หากใครกำลังค้นหาการอัปเดต useState() hooks สำหรับ object

ผ่านการป้อนข้อมูล

const [state, setState] = useState({ fName: "", lName: "" });
const handleChange = e => {
    const { name, value } = e.target;
    setState(prevState => ({
        ...prevState,
        [name]: value
    }));
};
<input
    value={state.fName}
    type="text"
    onChange={handleChange}
    name="fName"
/>
<input
    value={state.lName}
    type="text"
    onChange={handleChange}
    name="lName"
/>

ผ่าน onSubmit หรือ การคลิกปุ่ม

setState(prevState => ({
    ...prevState,
    fName: 'your updated value here'
}));

คำตอบ #3

โดยทั่วไปคุณควรระวังวัตถุที่ซ้อนลึกใน state ของ React เพื่อหลีกเลี่ยงพฤติกรรมที่ไม่คาดคิด state ควรถูกอัปเดตอย่างไม่เปลี่ยนแปลง เมื่อคุณมีวัตถุลึก คุณจะต้องทำการโคลนลึกเพื่อให้คงความไม่เปลี่ยนแปลง ซึ่งอาจมีค่าใช้จ่ายสูงใน React ทำไม?

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

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

ในกรณีที่คุณคิดเกี่ยวกับมัน แต่ยังมั่นใจว่าคุณจำเป็นต้องใช้โครงสร้างสถานะแบบซ้อนลึก คุณยังสามารถใช้ useState() ร่วมกับไลบรารีอย่าง immutable.js และ Immutability-helper ได้ พวกมันทำให้การอัปเดตหรือคัดลอกอ็อบเจ็กต์ลึกเป็นเรื่องง่ายโดยไม่ต้องกังวลเกี่ยวกับความสามารถในการเปลี่ยนแปลง

คำตอบ #4

ฉันมาสายงานปาร์ตี้.. :)

@aseferov คำตอบใช้ได้ผลดีมากเมื่อมีความตั้งใจที่จะป้อนโครงสร้างวัตถุทั้งหมดใหม่ อย่างไรก็ตาม หากเป้าหมาย/ความมุ่งหมายคือการอัปเดตค่าสำหรับฟิลด์เฉพาะในวัตถุ ผมเชื่อว่าวิธีด้านล่างนี้ดีกว่า

สถานการณ์:

const [infoData, setInfoData] = useState({
    major: {
      name: "John Doe",
      age: "24",
      sex: "M",
    },

    minor:{
      id: 4,
      collegeRegion: "south",

    }

  });

การอัปเดตระเบียนเฉพาะจะต้องมีการเรียกกลับไปยังสถานะก่อนหน้า prevState

ที่นี่ :

setInfoData((prevState) => ({
      ...prevState,
      major: {
        ...prevState.major,
        name: "Tan Long",
      }
    }));

บางที

setInfoData((prevState) => ({
      ...prevState,
      major: {
        ...prevState.major,
        name: "Tan Long",
      },
      minor: {
        ...prevState.minor,
        collegeRegion: "northEast"

    }));

ฉันหวังว่านี่จะช่วยใครก็ตามที่พยายามแก้ปัญหาที่คล้ายกันได้

คำตอบ #5

สามารถใช้ฮุค useReducer สำหรับการจัดการสถานะที่ซับซ้อน แทนการใช้ useState ในการทำเช่นนี้ ให้เริ่มต้นโดยการกำหนดสถานะและฟังก์ชันการอัปเดตดังนี้:

const initialState = { name: "Bob", occupation: "builder" };
const [state, updateState] = useReducer(
  (state, updates) => ({ ...state, ...updates }),
  initialState
);

แล้วคุณสามารถอัปเดตสถานะโดยส่งการอัปเดตบางส่วนเท่านั้น เช่นนี้:

updateState({ occupation: "postman" })

คำตอบ #6

ในปี 2022

หากคุณกำลังมองหาฟังก์ชันการทำงานเดียวกับ this.setState (มาจาก class components) ใน functional components นี่คือคำตอบที่จะช่วยคุณได้มาก

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

คุณมี state แบบด้านล่างและต้องการอัปเดตเฉพาะฟิลด์ที่ระบุจากทั้ง state ดังนั้นคุณจำเป็นต้องใช้ object destructing ทุกครั้ง และบางครั้งมันอาจทำให้รำคาญได้

const [state, setState] = useState({first: 1, second: 2});

// results will be state = {first: 3} instead of {first: 3, second: 2}
setState({first: 3})

// To resolve that you need to use object destructing every time
// results will be state = {first: 3, second: 2}
setState(prev => ({...prev, first: 3}))

เพื่อแก้ปัญหานั้น ฉันจึงคิดวิธี useReducer กรุณาตรวจสอบ useReducer

const stateReducer = (state, action) => ({
  ...state,
  ...(typeof action === 'function' ? action(state) : action),
});
const [state, setState] = useReducer(stateReducer, {first: 1, second: 2});

// results will be state = {first: 3, second: 2}
setState({first: 3})

// you can also access the previous state callback if you want
// results will remain same, state = {first: 3, second: 2}
setState(prev => ({...prev, first: 3}))

คุณสามารถเก็บ stateReducer ไว้ในไฟล์ utils และนำเข้าในทุกไฟล์ถ้าคุณต้องการได้

นี่คือตะขอที่กําหนดเองหากคุณต้องการ

import React from 'react';

export const stateReducer = (state, action) => ({
  ...state,
  ...(typeof action === 'function' ? action(state) : action),
});

const useReducer = (initial, lazyInitializer = null) => {
  const [state, setState] = React.useReducer(stateReducer, initial, init =>
    lazyInitializer ? lazyInitializer(init) : init
  );

  return [state, setState];
};

export default useReducer;

พิมพ์ดีด

import React, { Dispatch } from "react";

type SetStateAction<S> = S | ((prev: S) => S);

type STATE<R> = [R, Dispatch<SetStateAction<Partial<R>>>];

const stateReducer = (state, action) => ({
  ...state,
  ...(typeof action === "function" ? action(state) : action),
});

const useReducer = <S>(initial, lazyInitializer = null): STATE<S> => {
  const [state, setState] = React.useReducer(stateReducer, initial, (init) =>
    lazyInitializer ? lazyInitializer(init) : init,
  );

  return [state, setState];
};

export default useReducer;

คำตอบ #7

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

const [projectGroupDetails, setProjectGroupDetails] = useState({
    "projectGroupId": "",
    "projectGroup": "DDD",
    "project-id": "",
    "appd-ui": "",
    "appd-node": ""    
});

const inputGroupChangeHandler = (event) => {
    setProjectGroupDetails((prevState) => ({
       ...prevState,
       [event.target.id]: event.target.value
    }));
}

<Input 
    id="projectGroupId" 
    labelText="Project Group Id" 
    value={projectGroupDetails.projectGroupId} 
    onChange={inputGroupChangeHandler} 
/>


บทความที่เกี่ยวข้อง