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

javascript - useState() ใน React คืออะไร?


ฉันกำลังเรียนรู้แนวคิดของ hooks ใน React และพยายามทำความเข้าใจตัวอย่างด้านล่าง

import { useState } from 'react';

function Example() {
    // Declare a new state variable, which we'll call "count"
    const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

ตัวอย่างข้างต้นเพิ่มค่าตัวนับในตัวพารามิเตอร์ของฟังก์ชันตัวจัดการเอง ถ้าฉันต้องการแก้ไขค่าตัวนับภายในฟังก์ชันตัวจัดการเหตุการณ์ล่ะ

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

setCount = () => {
  //how can I modify count value here. Not sure if I can use setState to modify its value
  //also I want to modify other state values as well here. How can I do that
}

<button onClick={() => setCount()}>
  Click me
</button>

คำตอบ #1

React hooks เป็นวิธีใหม่ (ยังอยู่ระหว่างการพัฒนา) ในการเข้าถึงคุณสมบัติหลักของ React เช่น state โดยไม่ต้องใช้คลาส ในตัวอย่างของคุณ หากคุณต้องการเพิ่มตัวนับโดยตรงในฟังก์ชันตัวจัดการโดยไม่ระบุโดยตรงในพร็อพ onClick คุณสามารถทำบางอย่างเช่นนี้ได้:

...
const [count, setCounter] = useState(0);
const [moreStuff, setMoreStuff] = useState(...);
...

const setCount = () => {
    setCounter(count + 1);
    setMoreStuff(...);
    ...
};

และเมื่อคลิก:

<button onClick={setCount}>
    Click me
</button>

const [count, setCounter] = useState(0);

useState(0) จะส่งกลับเป็นทูเพิล โดยที่พารามิเตอร์ตัวแรก count คือสถานะปัจจุบันของเคาน์เตอร์ และ setCounter คือเมธอดที่จะช่วยให้เราสามารถอัปเดตสถานะของเคาน์เตอร์ได้ เราสามารถใช้เมธอด setCounter เพื่ออัปเดตสถานะของ count ได้ทุกที่ - ในกรณีนี้เรากำลังใช้งานมันภายในฟังก์ชัน setCount ซึ่งเราสามารถทำสิ่งต่าง ๆ ได้มากขึ้น; แนวคิดของ hooks คือเราสามารถทำให้โค้ดของเรามีความเป็นฟังก์ชันมากขึ้นและหลีกเลี่ยง คอมโพเนนต์แบบคลาส หากไม่ต้องการหรือไม่จำเป็น

ฉันเขียนบทความครบถ้วนเกี่ยวกับ hooks พร้อมตัวอย่างหลายตัวอย่าง (รวมถึง counters) เช่น codepen นี้ ฉันใช้ useState, useEffect, useContext, และ custom hooks ฉันสามารถอธิบายรายละเอียดเพิ่มเติมเกี่ยวกับการทำงานของ hooks ในคำตอบนี้ได้ แต่เอกสารนั้นอธิบายเกี่ยวกับ state hook และ hooks อื่น ๆ ได้อย่างละเอียดอยู่แล้ว

อัปเดต: Hooks ไม่ใช่ข้อเสนออีกต่อไป ตั้งแต่เวอร์ชัน 16.8 พวกมันสามารถใช้งานได้แล้ว มีส่วนหนึ่งในเว็บไซต์ของ React ที่ตอบคำถามที่พบบ่อยบางข้อ

คำตอบ #2

useState เป็นหนึ่งใน hook ของ react ที่มีอยู่ในเวอร์ชัน 0.16.7

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

const [state, setState] = useState(initialState);

ส่งกลับค่าที่มีสถานะ และฟังก์ชันเพื่อปรับปรุงมัน

ในระหว่างการเรนเดอร์เริ่มต้น สถานะที่ส่งกลับ (state) จะเหมือนกับค่าที่ส่งเป็นอาร์กิวเมนต์ตัวแรก (initialState)

ฟังก์ชัน setState ถูกใช้เพื่ออัปเดตสถานะ มันรับค่าสถานะใหม่และเข้าคิวการเรนเดอร์ซ้ำของคอมโพเนนต์

โปรดทราบ ว่า useState hook callback สำหรับการอัปเดตสถานะ มีพฤติกรรมแตกต่าง จาก this.setState ของคอมโพเนนต์ เพื่อแสดงให้คุณเห็นความแตกต่าง ผมได้เตรียมตัวอย่างสองตัวอย่างไว้

class UserInfoClass extends React.Component {
  state = { firstName: 'John', lastName: 'Doe' };
  
  render() {
    return <div>
      <p>userInfo: {JSON.stringify(this.state)}</p>
      <button onClick={() => this.setState({ 
        firstName: 'Jason'
      })}>Update name to Jason</button>
    </div>;
  }
}

// Please note that new object is created when setUserInfo callback is used
function UserInfoFunction() {
  const [userInfo, setUserInfo] = React.useState({ 
    firstName: 'John', lastName: 'Doe',
  });

  return (
    <div>
      <p>userInfo: {JSON.stringify(userInfo)}</p>
      <button onClick={() => setUserInfo({ firstName: 'Jason' })}>Update name to Jason</button>
    </div>
  );
}

ReactDOM.render(
  <div>
    <UserInfoClass />
    <UserInfoFunction />
  </div>
, document.querySelector('#app'));
<script src="https://unpkg.com/[email /umd/react.development.js"></script>
<script src="https://unpkg.com/[email /umd/react-dom.development.js"></script>

<div id="app"></div>

วัตถุใหม่จะถูกสร้างขึ้นเมื่อใช้ callback setUserInfo สังเกตว่าเราสูญเสียค่าของคีย์ lastName เพื่อแก้ไขปัญหานั้น เราสามารถส่งฟังก์ชันภายใน useState

setUserInfo(prevState => ({ ...prevState, firstName: 'Jason' })

ดูตัวอย่าง:

// Please note that new object is created when setUserInfo callback is used
function UserInfoFunction() {
  const [userInfo, setUserInfo] = React.useState({ 
    firstName: 'John', lastName: 'Doe',
  });

  return (
    <div>
      <p>userInfo: {JSON.stringify(userInfo)}</p>
      <button onClick={() => setUserInfo(prevState => ({
        ...prevState, firstName: 'Jason' }))}>
        Update name to Jason
      </button>
    </div>
  );
}

ReactDOM.render(
    <UserInfoFunction />
, document.querySelector('#app'));
<script src="https://unpkg.com/[email /umd/react.development.js"></script>
<script src="https://unpkg.com/[email /umd/react-dom.development.js"></script>

<div id="app"></div>

แตกต่างจากเมธอด setState ที่พบในคอมโพเนนต์แบบคลาส useState จะไม่ผสานวัตถุอัปเดตโดยอัตโนมัติ คุณสามารถจำลองพฤติกรรมนี้ได้โดยการรวมรูปแบบฟังก์ชันอัปเดตกับไวยากรณ์การกระจายวัตถุ:

setState(prevState => {
  // Object.assign would also work
  return {...prevState, ...updatedValues};
});

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับ useState ให้ดูเอกสารอย่างเป็นทางการ

คำตอบ #3

ไวยากรณ์ของฮุ๊ก useState นั้นตรงไปตรงมา

const [value, setValue] = useState(defaultValue)

ถ้าคุณไม่คุ้นเคยกับไวยากรณ์นี้ ให้ไปที่นี่

ผมขอแนะนำให้คุณอ่านเอกสาร มีคำอธิบายที่ดีเยี่ยมพร้อมตัวอย่างจำนวนพอสมควร

import { useState } from 'react';

function Example() {
    // Declare a new state variable, which we'll call "count"
    const [count, setCount] = useState(0);
  
  // its up to you how you do it
  const buttonClickHandler = e => {
   // increment
   // setCount(count + 1)
   
   // decrement
   // setCount(count -1)
   
   // anything
   // setCount(0)
  }
  

  return (
       <div>
          <p>You clicked {count} times</p>
         <button onClick={buttonClickHandler}>
             Click me
         </button>
      </div>
   );
 }

คำตอบ #4

useState() เป็น React hook Hooks ทำให้สามารถใช้ state และความสามารถในการเปลี่ยนแปลงภายในฟังก์ชันคอมโพเนนต์ได้

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

สำหรับตัวอย่างนี้ฉันจะใช้คอมโพเนนต์ตัวนับ นี่คือตัวมัน:

class Hello extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: props.count };
  }
  
  inc() {
    this.setState(prev => ({count: prev.count+1}));
  }
  
  render() {
    return <button onClick={() => this.inc()}>{this.state.count}</button>
  }
}

ReactDOM.render(<Hello count={0}/>, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id='root'></div>

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

function Hello(props) {
  class Hello extends React.Component {
    constructor(props) {
      super(props);
      this.state = { count: props.count };
    }

    inc() {
      this.setState(prev => ({count: prev.count+1}));
    }

    render() {
      return <button onClick={() => this.inc()}>{this.state.count}</button>
    }
  }
  return <Hello {...props}/>
}

ReactDOM.render(<Hello count={0}/>, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id='root'></div>

นี่คือคอมโพเนนต์เดียวกันเป๊ะ ๆ มีพฤติกรรมเดียวกัน ชื่อเดียวกัน และคุณสมบัติเดียวกัน ตอนนี้เรามายกสถานะการนับไปยังคอมโพเนนต์ฟังก์ชัน นี่คือลำดับขั้นตอน:

function Hello(props) {
  const [count, setCount] = React.useState(0);
  class Hello extends React.Component {
    constructor(props) {
      super(props);
      this.state = { count: props.count };
    }

    inc() {
      this.setState(prev => ({count: prev.count+1}));
    }

    render() {
      return <button onClick={() => setCount(count+1)}>{count}</button>
    }
  }
  return <Hello {...props}/>
}

ReactDOM.render(<Hello count={0}/>, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js" integrity="sha256-3vo65ZXn5pfsCfGM5H55X+SmwJHBlyNHPwRmWAPgJnM=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js" integrity="sha256-qVsF1ftL3vUq8RFOLwPnKimXOLo72xguDliIxeffHRc=" crossorigin="anonymous"></script>
<div id='root'></div>

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

function Hello(props) {
  const [count, setCount] = React.useState(0);

  return <button onClick={() => setCount(count+1)}>{count}</button>;
}

ReactDOM.render(<Hello count={0}/>, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js" integrity="sha256-3vo65ZXn5pfsCfGM5H55X+SmwJHBlyNHPwRmWAPgJnM=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js" integrity="sha256-qVsF1ftL3vUq8RFOLwPnKimXOLo72xguDliIxeffHRc=" crossorigin="anonymous"></script>

<div id='root'></div>

แม้ว่าสิ่งนี้จะทำให้สามารถใช้งานฮู๊กส์ภายในคอมโพเนนต์แบบคลาสได้ แต่ผมจะไม่แนะนำให้คุณทำเช่นนั้น เว้นแต่ว่าคุณกำลังย้ายเหมือนที่ผมทำในตัวอย่างนี้ การผสมระหว่างคอมโพเนนต์แบบฟังก์ชันและแบบคลาสจะทำให้การจัดการสถานะวุ่นวาย ผมหวังว่าสิ่งนี้จะช่วยได้

ด้วยความนับถือ

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