React Hooks useState () กับ Object


100

วิธีที่ถูกต้องในการอัปเดตสถานะคือวัตถุที่ซ้อนกันใน React with 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"
           }
        }
   })


คุณหมายถึงการเพิ่มค่าคีย์ออบเจ็กต์ใหม่ให้กับออบเจ็กต์ที่มีอยู่?
เพียงรหัส

@Justcode สำหรับตัวอย่างแรกใช่สำหรับตัวอย่างที่สองเพียงแค่เปลี่ยนวัตถุที่มีอยู่
isaacsultan

คำตอบ:


107

คุณสามารถส่งผ่านค่าใหม่เช่นนี้

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

2
เยี่ยมมากที่ตอบส่วนก! คุณรู้แนวทางปฏิบัติที่ดีที่สุดสำหรับตอน b หรือไม่?
isaacsultan

1
การเปลี่ยนแปลงเช่นเดียวกันในอดีตmasterFieldแทนที่จะเป็นmasterField2 setExampleState({...exampleState, masterField:{// new values}
aseferov

3
ระวังการคัดลอกแบบตื้นเมื่อใช้ตัวดำเนินการกระจาย ดูตัวอย่าง: stackoverflow.com/questions/43638938/…
João Marcos Gris

7
หากคุณใช้สถานะเดียวกันกับ Dispatcher คุณควรใช้ฟังก์ชัน setExampleState( exampleState => ({...exampleState, masterField2: {...etc} }) );
Michael Harley

41

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

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

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

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


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

40

หากใครกำลังค้นหา useState () hooks update for object

- Through Input

        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"
        />
   ***************************

 - Through onSubmit or button click
    
        setState(prevState => ({
            ...prevState,
            fName: 'your updated value here'
         }));

3
สิ่งที่ฉันกำลังมองหา สุดยอด!
StackedActor

7

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

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



6

มาสายไปปาร์ตี้ .. :)

คำตอบของ @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"

    }));

ฉันหวังว่านี่จะช่วยทุกคนที่พยายามแก้ปัญหาที่คล้ายกัน


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

1
@MichaelRamage ทำไมเราจึงใส่ฟังก์ชันไว้ในวงเล็บ():: เพื่อตอบสิ่งนี้ง่ายๆ เป็นเพราะsetInfoData มีลำดับสูงโดยธรรมชาติเช่นมันสามารถรับฟังก์ชั่นอื่นเป็นอาร์กิวเมนต์ได้รับความอนุเคราะห์จากอำนาจที่จัดหาโดย Hook: useState ฉันหวังว่าบทความนี้จะให้ความชัดเจนมากขึ้นเกี่ยวกับฟังก์ชันลำดับที่สูงขึ้น: sitepoint.com/higher-order-functions-javascript
Olamigoke Philip

1
มีประโยชน์มากโดยเฉพาะอย่างยิ่งสำหรับกรณีที่คุณพยายามอัปเดตคุณสมบัติที่ซ้อนกันหลายชั้นลึก ๆ เช่น: first.second. สาม - ตัวอย่างอื่น ๆ ครอบคลุมครั้งแรกที่สอง แต่ไม่ใช่อันดับแรกประการที่สาม
Craig Presti - MSFT

3
function App() {

  const [todos, setTodos] = useState([
    { id: 1, title: "Selectus aut autem", completed: false },
    { id: 2, title: "Luis ut nam facilis et officia qui", completed: false },
    { id: 3, title: "Fugiat veniam minus", completed: false },
    { id: 4, title: "Aet porro tempora", completed: true },
    { id: 5, title: "Laboriosam mollitia et enim quasi", completed: false }
  ]);

  const changeInput = (e) => {todos.map(items => items.id === parseInt(e.target.value) && (items.completed = e.target.checked));
 setTodos([...todos], todos);}
  return (
    <div className="container">
      {todos.map(items => {
        return (
          <div key={items.id}>
            <label>
<input type="checkbox" 
onChange={changeInput} 
value={items.id} 
checked={items.completed} />&nbsp; {items.title}</label>
          </div>
        )
      })}
    </div>
  );
}

1

ผมคิดว่าวิธีการแก้ปัญหาที่ดีที่สุดคืออิมเมอ ช่วยให้คุณสามารถอัปเดตวัตถุเช่นคุณกำลังแก้ไขฟิลด์โดยตรง (masterField.fieldOne.fieldx = 'abc') แต่จะไม่เปลี่ยนวัตถุจริงแน่นอน รวบรวมการอัปเดตทั้งหมดในวัตถุแบบร่างและให้ออบเจ็กต์สุดท้ายในตอนท้ายซึ่งคุณสามารถใช้เพื่อแทนที่วัตถุดั้งเดิมได้


0

ฉันปล่อยให้คุณมีฟังก์ชั่นยูทิลิตี้เพื่ออัปเดตวัตถุอย่างไม่สามารถเปลี่ยนแปลงได้

/**
 * Inmutable update object
 * @param  {Object} oldObject     Object to update
 * @param  {Object} updatedValues Object with new values
 * @return {Object}               New Object with updated values
 */
export const updateObject = (oldObject, updatedValues) => {
  return {
    ...oldObject,
    ...updatedValues
  };
};

คุณสามารถใช้แบบนี้ได้

const MyComponent = props => {

  const [orderForm, setOrderForm] = useState({
    specialities: {
      elementType: "select",
      elementConfig: {
        options: [],
        label: "Specialities"
      },
      touched: false
    }
  });


// I want to update the options list, to fill a select element

  // ---------- Update with fetched elements ---------- //

  const updateSpecialitiesData = data => {
    // Inmutably update elementConfig object. i.e label field is not modified
    const updatedOptions = updateObject(
      orderForm[formElementKey]["elementConfig"],
      {
        options: data
      }
    );
    // Inmutably update the relevant element.
    const updatedFormElement = updateObject(orderForm[formElementKey], {
      touched: true,
      elementConfig: updatedOptions
    });
    // Inmutably update the relevant element in the state.
    const orderFormUpdated = updateObject(orderForm, {
      [formElementKey]: updatedFormElement
    });
    setOrderForm(orderFormUpdated);
  };

  useEffect(() => {
      // some code to fetch data
      updateSpecialitiesData.current("specialities",fetchedData);
  }, [updateSpecialitiesData]);

// More component code
}

หากไม่มีคุณมียูทิลิตี้เพิ่มเติมที่นี่: https://es.reactjs.org/docs/update.html


0

ตอนแรกฉันใช้ object ใน useState แต่จากนั้นฉันก็ย้ายไปใช้ hookReducer สำหรับเคสที่ซับซ้อน ฉันรู้สึกว่ามีการปรับปรุงประสิทธิภาพเมื่อฉันปรับโครงสร้างโค้ดใหม่

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

useReducer React เอกสาร

ฉันติดตั้งเบ็ดดังกล่าวสำหรับการใช้งานของตัวเองแล้ว:

/**
 * Same as useObjectState but uses useReducer instead of useState
 *  (better performance for complex cases)
 * @param {*} PropsWithDefaultValues object with all needed props 
 * and their initial value
 * @returns [state, setProp] state - the state object, setProp - dispatch 
 * changes one (given prop name & prop value) or multiple props (given an 
 * object { prop: value, ...}) in object state
 */
export function useObjectReducer(PropsWithDefaultValues) {
  const [state, dispatch] = useReducer(reducer, PropsWithDefaultValues);

  //newFieldsVal={[field_name]: [field_value], ...}
  function reducer(state, newFieldsVal) {
    return { ...state, ...newFieldsVal };
  }

  return [
    state,
    (newFieldsVal, newVal) => {
      if (typeof newVal !== "undefined") {
        const tmp = {};
        tmp[newFieldsVal] = newVal;
        dispatch(tmp);
      } else {
        dispatch(newFieldsVal);
      }
    },
  ];
}

ที่เกี่ยวข้องเพิ่มเติมตะขอ

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