欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

React?18?如何更新?state?中的對象

 更新時間:2023年08月19日 09:14:45   作者:木藍茶陌*_*  
state 中可以保存任意類型的JavaScript值,包括對象,但是,不應該直接修改存放在 React state 中的對象,這篇文章主要介紹了React?18更新state中的對象,需要的朋友可以參考下

參考文章

更新 state 中的對象

state 中可以保存任意類型的 JavaScript 值,包括對象。但是,不應該直接修改存放在 React state 中的對象。相反,當想要更新一個對象時,需要創(chuàng)建一個新的對象(或者將其拷貝一份),然后將 state 更新為此對象。

什么是 mutation?

可以在 state 中存放任意類型的 JavaScript 值。

const [x, setX] = useState(0);

在 state 中存放數字、字符串和布爾值,這些類型的值在 JavaScript 中是不可變(immutable)的,這意味著它們不能被改變或是只讀的??梢酝ㄟ^替換它們的值以觸發(fā)一次重新渲染。

setX(5);

state x 0 變?yōu)?5 ,但是數字 0 本身并沒有發(fā)生改變。在 JavaScript 中,無法對內置的原始值,如數字、字符串和布爾值,進行任何更改。

現在考慮 state 中存放對象的情況:

const [position, setPosition] = useState({ x: 0, y: 0 });

從技術上來講,可以改變對象自身的內容。當這樣做時,就制造了一個 mutation

position.x = 5;

然而,雖然嚴格來說 React state 中存放的對象是可變的,但應該像處理數字、布爾值、字符串一樣將它們視為不可變的。因此應該替換它們的值,而不是對它們進行修改。

將 state 視為只讀的

換句話說,應該 把所有存放在 state 中的 JavaScript 對象都視為只讀的。

在下面的例子中,用一個存放在 state 中的對象來表示指針當前的位置。當在預覽區(qū)觸摸或移動光標時,紅色的點本應移動。但是實際上紅點仍停留在原處:

import { useState } from 'react';
export default function MovingDot() {
  const [position, setPosition] = useState({
    x: 0,
    y: 0
  });
  return (
    <div
      onPointerMove={e => {
        position.x = e.clientX;
        position.y = e.clientY;
      }}
      style={{
        position: 'relative',
        width: '100vw',
        height: '100vh',
      }}>
      <div style={{
        position: 'absolute',
        backgroundColor: 'red',
        borderRadius: '50%',
        transform: `translate(${position.x}px, ${position.y}px)`,
        left: -10,
        top: -10,
        width: 20,
        height: 20,
      }} />
    </div>
  );
}

問題出在下面這段代碼中。

onPointerMove={e => {
  position.x = e.clientX;
  position.y = e.clientY;
}}

這段代碼直接修改了 上一次渲染中 分配給 position 的對象。但是因為并沒有使用 state 的設置函數,React 并不知道對象已更改。所以 React 沒有做出任何響應。雖然在一些情況下,直接修改 state 可能是有效的,但并不推薦這么做。應該把在渲染過程中可以訪問到的 state 視為只讀的。

在這種情況下,為了真正地 觸發(fā)一次重新渲染,需要創(chuàng)建一個新對象并把它傳遞給 state 的設置函數

onPointerMove={e => {
  setPosition({
    x: e.clientX,
    y: e.clientY
  });
}}

通過使用 setPosition ,在告訴 React:

  • 使用這個新的對象替換 position 的值
  • 然后再次渲染這個組件

現在可以看到,當在預覽區(qū)觸摸或移動光標時,紅點會跟隨著指針移動:

import { useState } from 'react';
export default function MovingDot() {
  const [position, setPosition] = useState({
    x: 0,
    y: 0
  });
  return (
    <div
      onPointerMove={e => {
        setPosition({
          x: e.clientX,
          y: e.clientY
        });
      }}
      style={{
        position: 'relative',
        width: '100vw',
        height: '100vh',
      }}>
      <div style={{
        position: 'absolute',
        backgroundColor: 'red',
        borderRadius: '50%',
        transform: `translate(${position.x}px, ${position.y}px)`,
        left: -10,
        top: -10,
        width: 20,
        height: 20,
      }} />
    </div>
  );
}

使用展開語法復制對象

在之前的例子中,始終會根據當前指針的位置創(chuàng)建出一個新的 position 對象。但是通常,會希望把 現有 數據作為所創(chuàng)建的新對象的一部分。例如,可能只想要更新表單中的一個字段,其他的字段仍然使用之前的值。

下面的代碼中,輸入框并不會正常運行,因為 onChange 直接修改了 state :

import { useState } from 'react';
export default function Form() {
  const [person, setPerson] = useState({
    firstName: 'Barbara',
    lastName: 'Hepworth',
    email: 'bhepworth@sculpture.com'
  });
  function handleFirstNameChange(e) {
    person.firstName = e.target.value;
  }
  function handleLastNameChange(e) {
    person.lastName = e.target.value;
  }
  function handleEmailChange(e) {
    person.email = e.target.value;
  }
  return (
    <>
      <label>
        First name:
        <input
          value={person.firstName}
          onChange={handleFirstNameChange}
        />
      </label>
      <label>
        Last name:
        <input
          value={person.lastName}
          onChange={handleLastNameChange}
        />
      </label>
      <label>
        Email:
        <input
          value={person.email}
          onChange={handleEmailChange}
        />
      </label>
      <p>
        {person.firstName}{' '}
        {person.lastName}{' '}
        ({person.email})
      </p>
    </>
  );
}

例如,下面這行代碼修改了上一次渲染中的 state:

person.firstName = e.target.value;

想要實現需求,最可靠的辦法就是創(chuàng)建一個新的對象并將它傳遞給 setPerson 。但是在這里,還需要 把當前的數據復制到新對象中,因為只改變了其中一個字段:

setPerson({
  firstName: e.target.value, // 從 input 中獲取新的 first name
  lastName: person.lastName,
  email: person.email
});

可以使用 ... 對象展開 語法,這樣就不需要單獨復制每個屬性。

setPerson({
  ...person, // 復制上一個 person 中的所有字段
  firstName: e.target.value // 但是覆蓋 firstName 字段 
});

現在表單可以正常運行了!

可以看到,并沒有為每個輸入框單獨聲明一個 state。對于大型表單,將所有數據都存放在同一個對象中是非常方便的——前提是能夠正確地更新它!

import { useState } from 'react';
export default function Form() {
  const [person, setPerson] = useState({
    firstName: 'Barbara',
    lastName: 'Hepworth',
    email: 'bhepworth@sculpture.com'
  });
  function handleFirstNameChange(e) {
    setPerson({
      ...person,
      firstName: e.target.value
    });
  }
  function handleLastNameChange(e) {
    setPerson({
      ...person,
      lastName: e.target.value
    });
  }
  function handleEmailChange(e) {
    setPerson({
      ...person,
      email: e.target.value
    });
  }
  return (
    <>
      <label>
        First name:
        <input
          value={person.firstName}
          onChange={handleFirstNameChange}
        />
      </label>
      <label>
        Last name:
        <input
          value={person.lastName}
          onChange={handleLastNameChange}
        />
      </label>
      <label>
        Email:
        <input
          value={person.email}
          onChange={handleEmailChange}
        />
      </label>
      <p>
        {person.firstName}{' '}
        {person.lastName}{' '}
        ({person.email})
      </p>
    </>
  );
}

請注意 ... 展開語法本質是“淺拷貝”——它只會復制一層。這使得它的執(zhí)行速度很快,但是也意味著當想要更新一個嵌套屬性時,必須得多次使用展開語法。

更新一個嵌套對象

考慮下面這種結構的嵌套對象:

const [person, setPerson] = useState({
  name: 'Niki de Saint Phalle',
  artwork: {
    title: 'Blue Nana',
    city: 'Hamburg',
    image: 'https://i.imgur.com/Sd1AgUOm.jpg',
  }
});

如果想要更新 person.artwork.city 的值,用 mutation 來實現的方法非常容易理解:

person.artwork.city = 'New Delhi';

但是在 React 中,需要將 state 視為不可變的!為了修改 city 的值,首先需要創(chuàng)建一個新的 artwork 對象(其中預先填充了上一個 artwork 對象中的數據),然后創(chuàng)建一個新的 person 對象,并使得其中的 artwork 屬性指向新創(chuàng)建的 artwork 對象:

const nextArtwork = { ...person.artwork, city: 'New Delhi' };
const nextPerson = { ...person, artwork: nextArtwork };
setPerson(nextPerson);

或者,寫成一個函數調用:

setPerson({
  ...person, // 復制其它字段的數據 
  artwork: { // 替換 artwork 字段 
    ...person.artwork, // 復制之前 person.artwork 中的數據
    city: 'New Delhi' // 但是將 city 的值替換為 New Delhi!
  }
});

這雖然看起來有點冗長,但對于很多情況都能有效地解決問題:

import { useState } from 'react';
export default function Form() {
  const [person, setPerson] = useState({
    name: 'Niki de Saint Phalle',
    artwork: {
      title: 'Blue Nana',
      city: 'Hamburg',
      image: 'https://i.imgur.com/Sd1AgUOm.jpg',
    }
  });
  function handleNameChange(e) {
    setPerson({
      ...person,
      name: e.target.value
    });
  }
  function handleTitleChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        title: e.target.value
      }
    });
  }
  function handleCityChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        city: e.target.value
      }
    });
  }
  function handleImageChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        image: e.target.value
      }
    });
  }
  return (
    <>
      <label>
        Name:
        <input
          value={person.name}
          onChange={handleNameChange}
        />
      </label>
      <label>
        Title:
        <input
          value={person.artwork.title}
          onChange={handleTitleChange}
        />
      </label>
      <label>
        City:
        <input
          value={person.artwork.city}
          onChange={handleCityChange}
        />
      </label>
      <label>
        Image:
        <input
          value={person.artwork.image}
          onChange={handleImageChange}
        />
      </label>
      <p>
        <i>{person.artwork.title}</i>
        {' by '}
        {person.name}
        <br />
        (located in {person.artwork.city})
      </p>
      <img 
        src={person.artwork.image} 
        alt={person.artwork.title}
      />
    </>
  );
}

使用 Immer 編寫簡潔的更新邏輯

如果 state 有多層的嵌套,或許應該考慮 將其扁平化。但是,如果不想改變 state 的數據結構,可以使用 Immer 來實現嵌套展開的效果。Immer 是一個非常流行的庫,它可以讓你使用簡便但可以直接修改的語法編寫代碼,并會幫你處理好復制的過程。通過使用 Immer,寫出的代碼看起來就像是“打破了規(guī)則”而直接修改了對象:

updatePerson(draft => {
  draft.artwork.city = 'Lagos';
});

但是不同于一般的 mutation,它并不會覆蓋之前的 state!

嘗試使用 Immer:

  • 運行 npm install use-immer 添加 Immer 依賴
  • import { useImmer } from 'use-immer' 替換掉 import { useState } from 'react'

下面我們把上面的例子用 Immer 實現一下:

import { useImmer } from 'use-immer';
export default function Form() {
  const [person, updatePerson] = useImmer({
    name: 'Niki de Saint Phalle',
    artwork: {
      title: 'Blue Nana',
      city: 'Hamburg',
      image: 'https://i.imgur.com/Sd1AgUOm.jpg',
    }
  });
  function handleNameChange(e) {
    updatePerson(draft => {
      draft.name = e.target.value;
    });
  }
  function handleTitleChange(e) {
    updatePerson(draft => {
      draft.artwork.title = e.target.value;
    });
  }
  function handleCityChange(e) {
    updatePerson(draft => {
      draft.artwork.city = e.target.value;
    });
  }
  function handleImageChange(e) {
    updatePerson(draft => {
      draft.artwork.image = e.target.value;
    });
  }
  return (
    <>
      <label>
        Name:
        <input
          value={person.name}
          onChange={handleNameChange}
        />
      </label>
      <label>
        Title:
        <input
          value={person.artwork.title}
          onChange={handleTitleChange}
        />
      </label>
      <label>
        City:
        <input
          value={person.artwork.city}
          onChange={handleCityChange}
        />
      </label>
      <label>
        Image:
        <input
          value={person.artwork.image}
          onChange={handleImageChange}
        />
      </label>
      <p>
        <i>{person.artwork.title}</i>
        {' by '}
        {person.name}
        <br />
        (located in {person.artwork.city})
      </p>
      <img 
        src={person.artwork.image} 
        alt={person.artwork.title}
      />
    </>
  );
}

package.json:

{
  "dependencies": {
    "immer": "1.7.3",
    "react": "latest",
    "react-dom": "latest",
    "react-scripts": "latest",
    "use-immer": "0.5.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  },
  "devDependencies": {}
}

可以看到,事件處理函數變得更簡潔了??梢噪S意在一個組件中同時使用 useStateuseImmer。如果想要寫出更簡潔的更新處理函數,Immer 會是一個不錯的選擇,尤其是當 state 中有嵌套,并且復制對象會帶來重復的代碼時。

摘要

  • 將 React 中所有的 state 都視為不可直接修改的。
  • 當在 state 中存放對象時,直接修改對象并不會觸發(fā)重渲染,并會改變前一次渲染“快照”中 state 的值。
  • 不要直接修改一個對象,而要為它創(chuàng)建一個 新 版本,并通過把 state 設置成這個新版本來觸發(fā)重新渲染。
  • 可以使用這樣的 {...obj, something: 'newValue'} 對象展開語法來創(chuàng)建對象的拷貝。
  • 對象的展開語法是淺層的:它的復制深度只有一層。
  • 想要更新嵌套對象,需要從更新的位置開始自底向上為每一層都創(chuàng)建新的拷貝。
  • 想要減少重復的拷貝代碼,可以使用 Immer。

到此這篇關于React 18 更新 state 中的對象的文章就介紹到這了,更多相關React更新 state 對象內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • useEffect?返回函數執(zhí)行過程源碼解析

    useEffect?返回函數執(zhí)行過程源碼解析

    這篇文章主要為大家介紹了useEffect?返回函數執(zhí)行過程源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-04-04
  • react-diagram 序列化Json解讀案例分析

    react-diagram 序列化Json解讀案例分析

    今天帶來大家學習react-diagram 序列化Json解讀的相關知識,本文通過多種案例給大家分析序列化知識,通過圖文并茂的形式給大家介紹的非常詳細,感興趣的朋友一起看看吧
    2021-05-05
  • react?hooks中的useState使用要點

    react?hooks中的useState使用要點

    這篇文章主要為大家介紹了react?hooks中的useState使用要點詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-09-09
  • React+Antd 實現可增刪改表格的示例

    React+Antd 實現可增刪改表格的示例

    這篇文章主要介紹了React+Antd實現可增刪改表格的示例,幫助大家更好的理解和學習使用React,感興趣的朋友可以了解下
    2021-04-04
  • 基于CSS實現MaterialUI按鈕點擊動畫并封裝成 React 組件

    基于CSS實現MaterialUI按鈕點擊動畫并封裝成 React 組件

    筆者先后開發(fā)過基于vue,react,angular等框架的項目,碧如vue生態(tài)的elementUI, ant-design-vue, iView等成熟的UI框架, react生態(tài)的ant-design, materialUI等,這些第三方UI框架極大的降低了我們開發(fā)一個項目的成本和復雜度,使開發(fā)者更專注于實現業(yè)務邏輯和服務化
    2021-11-11
  • React組件的生命周期深入理解分析

    React組件的生命周期深入理解分析

    組件的生命周期就是React的工作過程,就好比人有生老病死,自然界有日月更替,每個組件在網頁中也會有被創(chuàng)建、更新和刪除,如同有生命的機體一樣
    2022-12-12
  • 詳細談談React中setState是一個宏任務還是微任務

    詳細談談React中setState是一個宏任務還是微任務

    學過react的人都知道,setState在react里是一個很重要的方法,使用它可以更新我們數據的狀態(tài),下面這篇文章主要給大家介紹了關于React中setState是一個宏任務還是微任務的相關資料,需要的朋友可以參考下
    2021-09-09
  • React-Route6實現keep-alive效果

    React-Route6實現keep-alive效果

    本文主要介紹了React-Route6實現keep-alive效果,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧<BR>
    2022-06-06
  • React.InputHTMLAttributes實踐和注意事項

    React.InputHTMLAttributes實踐和注意事項

    文章討論了如何在封裝組件中使用React.InputHTMLAttributes,以及如何通過 {...inputProps} 動態(tài)傳遞屬性,最后,文章總結了使用React.InputHTMLAttributes的最佳實踐和注意事項,感興趣的朋友一起看看吧
    2024-11-11
  • ReactRouterV6如何獲取當前路由參數

    ReactRouterV6如何獲取當前路由參數

    這篇文章主要介紹了ReactRouterV6如何獲取當前路由參數問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-03-03

最新評論