โดย "จูโบเย จอห์นสัน"
useReducer
React hook นั้นซับซ้อนและซับซ้อนเล็กน้อยในการปรับขนาดในตอนแรก ถึงกระนั้น หลังจากที่คุณเข้าใจเบ็ดนี้และวิธีการใช้งานแล้ว มันทำให้การจัดการสถานะง่ายที่สุดเท่าที่จะเป็นไปได้ โดยเฉพาะอย่างยิ่งในด้านการติดตามของสถานะส่วนต่าง ๆ ที่ใช้งานโดยใช้เบ็ด useState
ในบทความนี้ เราจะดูที่ฮุก useReducer
และเหตุใดจึงเป็นตัวเลือกที่ดีกว่าสำหรับการจัดการสถานะที่ซับซ้อนใน React มากกว่าฮุก useState
บทช่วยสอนนี้เหมาะสำหรับผู้เริ่มต้น และคุณต้องติดตั้ง Node.js และ React
การจัดการของรัฐ
เรามักจะได้ยินเกี่ยวกับสถานะ การเปลี่ยนแปลงของรัฐ และการจัดการของรัฐ รัฐคืออะไรกันแน่? เราเข้าใจสิ่งนี้อย่างแท้จริงโดยบอกว่านี่คือสถานะปัจจุบันของโปรแกรมของคุณ แต่นั่นอาจไม่ง่ายเกินไปที่จะเข้าใจ ในการเขียนโปรแกรม สถานะเป็นเพียงการรวมกันของข้อมูลทั้งหมดที่เรามีในแอปพลิเคชันของเรา ซึ่งเป็นข้อมูลที่ใช้และส่งคืนโดยโปรแกรมที่กำลังดำเนินอยู่ของคุณ
การจัดการของรัฐคืออะไรกันแน่? ตามวิกิพีเดีย “ การจัดการสถานะ หมายถึงการจัดการสถานะของการควบคุมอินเทอร์เฟซผู้ใช้ตั้งแต่หนึ่งรายการขึ้นไป เช่น ช่องข้อความ ปุ่มตกลง ปุ่มตัวเลือก ฯลฯ”
useReduce เทียบกับ useState
การจัดการสถานะสามารถทำได้โดยไม่ต้องใช้ useState
หรือไม่ คำถามที่แพร่หลายซึ่งมีผลลัพธ์มากกว่า 300,000 รายการบน Google:
หากคุณพบว่าตัวเองติดตามสถานะหลายส่วนที่ต้องอาศัยตรรกะที่ซับซ้อน เบ็ด useReducer
อาจจะดีกว่า มาสร้างแอปที่สามารถเพิ่มและลดจำนวนโดยใช้เบ็ดนั้นและดูว่ามันจะมีประสิทธิภาพเพียงใด
การตั้งค่าพื้นที่พัฒนา
เราจำเป็นต้องเรียกใช้สิ่งนี้:
npx create-react-app counter
cd counter
npm start
หลังจากติดตั้งแล้วเราควรจะมีสิ่งนี้
ใช้ลด
const [state, dispatch] = useReducer(reducer, initialArg, init);
UseReducer ส่งคืนอาร์เรย์โดยองค์ประกอบแรกเป็นสถานะและองค์ประกอบที่สองเป็นฟังก์ชันการจัดส่งที่จะเรียกใช้ useReducer
ในการสร้างแอปพลิเคชันตัวนับ เราจำเป็นต้องมีไฟล์สี่ไฟล์: ไฟล์หลัก App.js
เพื่อเรนเดอร์ส่วนประกอบของเรา Counter.js
สำหรับการสมัครโต้แย้งของเรา Reducer.js
โดยที่เราจะจัดการสถานะแอปพลิเคชันโดยใช้ตรรกะ useReducer ของเรา และ Styles.css
ของเรา คำถามเกิดขึ้น: เรากำลังทำอะไรอยู่? เราจะบริหารจัดการรัฐอย่างไร? สิ่งนี้จะให้ประโยชน์อะไรบ้างมากกว่า useState
และคำถามก็ดำเนินต่อไป ฉันจะตอบคำถามเหล่านี้ทั้งหมดในบทความนี้
เล่นซ้ำเซสชันโอเพ่นซอร์ส
OpenReplay เป็นชุดการเล่นซ้ำเซสชันแบบโอเพ่นซอร์สที่ช่วยให้คุณเห็นว่าผู้ใช้ทำอะไรบนเว็บแอปของคุณ ซึ่งช่วยให้คุณแก้ไขปัญหาได้เร็วขึ้น OpenReplay โฮสต์ในตัวเองเพื่อการควบคุมข้อมูลของคุณอย่างสมบูรณ์
เริ่มเพลิดเพลินกับประสบการณ์การแก้ไขข้อบกพร่องของคุณ — เริ่มใช้ OpenReplay ฟรี.
การสมัครเคาน์เตอร์
นี่คือวิธีที่เราสามารถเริ่มต้นสิ่งนี้ได้
const [count, dispatch] = useReducer(reducer, 0);
แทนที่จะเป็นตัวตั้งค่าใน useState
เราใช้ dispatch
'Dispatch' ในที่นี้มีความหมายตามตัวอักษร เหมือนกับว่าคุณต้องการส่งอะไรบางอย่างมากกว่า: คุณสามารถพูดว่า 'send an action' เราจะประมวลผลด้วยฟังก์ชันตัวลด ดังที่เราเห็นด้านบนนี้ เรามีสถานะ 0 มาเริ่มสร้างแอปพลิเคชันตัวนับกันดีกว่า
//counter.js
import React, { useReducer } from "react";
import reducer from "./Reducer";
function Counter() {
const [count, dispatch] = useReducer(reducer, 0);
return (
<div className="container">
<div className="card">
<h1>Counter Application</h1>
<h3>{count}</h3>
<div>
<button className="btn1" onClick={() => dispatch("increment")}>
increment
</button>
<button className="btn2" onClick={() => dispatch("decrement")}>
decrement
</button>
<button className="btn3" onClick={() => dispatch("reset")}>
Reset
</button>
</div>
</div>
</div>
);
}
export default Counter;
เรายังมี:
//reducer.js
const reducer = (state, action) => {
if (action === "increment") {
return state + 1;
} else if (action === "decrement") {
return state - 1;
} else if (action === "reset") {
return 0;
} else {
throw new Error();
}
};
export default reducer;
และสไตล์:
//styles.css
.container {
display: flex;
align-items: center;
justify-content: center;
}
h3 {
display: flex;
align-items: center;
justify-content: center;
}
.btn1 {
background-color: blue;
margin: 20px;
color: beige;
}
.btn2 {
background-color: red;
margin: 20px;
color: beige
}
.btn3 {
background-color: green;
margin: 20px;
color: beige
}
สุดท้าย ไฟล์ App
หลักของเรา
//App.js
import React from "react";
import "./styles.css";
import Counter from "./Counter";
function App() {
return (
<div>
<Counter />
</div>
);
}
export default App;
โค้ดด้านบนแสดงแอปพลิเคชันตัวนับซึ่งสถานะได้รับการจัดการโดยตะขอ useReducer ตัวนับไม่ได้สอนมากนักเกี่ยวกับการจัดการสถานะที่ซับซ้อน แต่ฉันจะอธิบายตรรกะที่ใช้ข้างต้น ตัวลดใช้เวลาในสถานะของเราและการดำเนินการที่ส่งไป ใน Reducer.js
ฟังก์ชันตัวลดจะเข้าสู่สถานะของเรา และการดำเนินการจะถูกจัดส่ง จากนั้นเราจะใช้คำสั่งแบบมีเงื่อนไข เรามี if-else
ของเราและผ่านไปใน action.type
ของเรา เราส่ง increment
, decrement
และ reset
ไปยังฟังก์ชัน onclick
ใน JSX
ขณะนี้เราสามารถทดสอบแอปพลิเคชันของเราได้แล้วหากทำงานได้ดี เริ่มแรกเรามี:
หลังจากเพิ่มขึ้นทีละน้อย เราจะได้:
นี่คือจุดที่เราเริ่มเห็น useReducer
ส่องแสงมาที่เรา เราอาจไม่ได้สังเกตว่าเราได้แยกตรรกะการอัปเดตสถานะของเราออกจากส่วนประกอบของเราโดยสิ้นเชิง ขณะนี้เรากำลังจับคู่การกระทำกับการเปลี่ยนสถานะ และตอนนี้เราสามารถแยกวิธีที่สถานะอัปเดตจากการกระทำที่เกิดขึ้นได้ (เราจะเจาะลึกถึงประโยชน์เชิงปฏิบัติมากขึ้นในภายหลัง) สำหรับตอนนี้ เรามาเพิ่มคุณสมบัติที่ซับซ้อนมากขึ้นให้กับแอปของเราเพื่ออธิบายให้ดีขึ้นว่า useReducer
สะดวกสบายเพียงใด
แทนที่จะเพิ่มหรือลดทีละ 1 มาสร้างแถบเลื่อนที่ผู้ใช้สามารถเลือกค่าที่ต้องการเพิ่มหรือลดได้ตั้งแต่ 1 ถึง 100
import React, { useState } from "react";
function Slider({ onchange, min, max }) {
const [value, setvalue] = useState(1);
return (
<div className="slide">
{value}
<input
type="range"
min={min}
max={max}
value={value}
onChange={(e) => {
const value = Number(e.target.value);
onchange(value);
setvalue(value);
}}
/>
</div>
);
}
export default Slider;
เราจำเป็นต้องนำเข้าสิ่งนี้ใน Counter.js ของเราเพื่อให้สามารถแสดงผลบนเบราว์เซอร์ได้ นอกจากนี้ เราจะส่งผ่านอุปกรณ์ประกอบฉาก min
, max
และ onchange
ที่ให้ค่าเหล่านั้นด้วย
import Slide from "./Slide";
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0, step: 1 });
return (
<div className='container'>
<div className='card'>
<h1>Counter Application</h1>
<h3>{state.count}</h3>
<div >
<button className='btn1' onClick={() => dispatch('increment')}>increment</button>
<button className='btn2' onClick={() => dispatch('decrement')}>decrement</button>
<button className='btn3' onClick={() => dispatch('reset')}>Reset</button>
</div>
<div>
<Slide
min={1}
max={100}
onchange={()=>({})}
/>
</div>
</div>
</div>
);
}
นี่คือสิ่งที่เราได้รับ
ตอนนี้เราสามารถรับค่าของตัวเลื่อนได้จากเสา onChange
สิ่งนี้ช่วยให้เราตัดสินใจว่าเราจะเพิ่มและลดค่าได้มากเพียงใด เราจำเป็นต้องทำการเปลี่ยนแปลงเล็กน้อยเพื่อจัดการส่วนของสถานะของค่าตัวเลื่อนของเรา และเปิดใช้งานตัวเลื่อนของเราเพื่อกำหนดสิ่งที่เราจะเพิ่มหรือลด
มาทำให้สถานะของเราเป็นอ็อบเจ็กต์กันดีกว่า ดังนั้น สถานะใหม่ใดๆ ที่ตัวลดของเราต้องจัดการสามารถไปเป็นทรัพย์สินของวัตถุนั้นได้ ขั้นแรก เราเปลี่ยนสถานะเริ่มต้นของเราให้เป็นวัตถุ
const [state, dispatch] = useReducer(reducer, { count: 0, move: 1 });
รัฐของเราเป็นวัตถุ เราจำเป็นต้องอัปเดตตัวลดของเราเพื่อส่งคืนวัตถุที่มีคุณสมบัติสองประการ
const reducer = (state, action) => {
if (action === "increment") {
return {
count: state.count + 1,
move: state.move,
};
} else if (action === "decrement") {
return {
count: state.count - 1,
move: state.move,
};
} else if (action === "reset") {
return {
count: 0,
move: state.move,
};
} else {
throw new Error();
}
};
export default reducer;
กลับมาที่เคาน์เตอร์ เราต้องส่งสถานะไปที่ JSX
ของเรา
<h3>{state.count}</h3>
มันใช้งานได้ดี แต่แทนที่จะให้สถานะของเราเป็นจำนวนเต็ม ตอนนี้เรามีมันเป็นวัตถุ ซึ่งช่วยให้เราสามารถส่งผ่านคุณสมบัติอื่น ๆ ได้ ตอนนี้คำถามก็ปรากฏขึ้น: คุณต้องการส่งอะไรใน onChange
เพื่ออัปเดตสถานะของตัวลดของเรา ถึงตอนนี้ เราสามารถส่งประเภทของการกระทำที่เกิดขึ้นได้ (เพิ่มขึ้น ลด หรือรีเซ็ต) นั่นใช้ได้ผลดี แต่ตอนนี้เรากำลังพบกับข้อจำกัดของมัน นอกจาก action type
แล้ว ยังจำเป็นต้องมีข้อมูลเพิ่มเติมอีกด้วย โดยเฉพาะเราต้องส่งผ่านค่าของสไลด์เพื่อเพิ่มลงในค่าสถานะของเราและอัปเดตสถานะ แทนที่จะให้การกระทำของเราส่งผ่านเป็นสตริง มาเปลี่ยนเป็นวัตถุที่มีคุณสมบัติประเภทกันดีกว่า ด้วยวิธีนี้ เรายังคงสามารถจัดส่งตามประเภทการดำเนินการได้ เราจะสามารถส่งค่าของแถบเลื่อนและข้อมูลอื่น ๆ เป็นคุณสมบัติในวัตถุการกระทำได้ เราสามารถไปที่ onChange prop ของเราและทำสิ่งนี้ให้เสร็จได้ทันที
<Slide
min={1}
max={100}
onchange={(value) =>
dispatch({
type: "stepUpdate",
step: value,
})
}
/>;
มีการเปลี่ยนแปลงสามประการที่เราต้องทำกับตัวลดของเรา:
- เราจำเป็นต้องอัปเดตส่วนเพิ่มและส่วนลดเพื่อปรับการนับตามคุณสมบัติขั้นตอน ไม่ใช่แค่ 1 เราทำสิ่งนี้โดยอัปเดตด้วยอะไรก็ตามที่
move
เป็น - เราจำเป็นต้องคำนึงถึงประเภทการกระทำใหม่
moveUpdate
ด้วยการเพิ่มตัวพิมพ์ลงในตัวลดของเรา - เราจำเป็นต้องเปลี่ยน
action
ให้เป็นวัตถุแทนที่จะเป็นสตริงโดยส่งคุณสมบัติ type ไปยังกรณีใหม่ของเราเท่านั้น
มาทำการแก้ไขด่วนเหล่านั้นกัน
//Reducer.js
const reducer = (state, action) => {
if (action === "increment") {
return {
count: state.count + state.move,
move: state.move,
};
} else if (action === "decrement") {
return {
count: state.count - state.move,
move: state.move,
};
} else if (action === "reset") {
return {
count: 0,
move: state.move,
};
} else if (action.type === "moveUpdate") {
return {
count: state.count,
move: action.move,
};
} else {
throw new Error();
}
};
export default reducer;
ขณะนี้เราสามารถอัปเดตค่าการนับได้โดยใช้แถบเลื่อน เช่น เพิ่มจากศูนย์ก่อนเป็น 31 และจากนั้นเป็น 48
บทสรุป
เรามาถึงจุดสิ้นสุดของบทความแล้ว แต่ฉันต้องอธิบายบางสิ่งที่สำคัญอย่างชัดเจน เราได้เห็นคุณประโยชน์อันเหลือเชื่อและทรงพลังของ useReducer
ที่คุณอาจพลาดไปแล้ว: ฟังก์ชันตัวลดส่งผ่านสถานะปัจจุบันเป็นอาร์กิวเมนต์แรก ด้วยเหตุนี้ จึงเป็นเรื่องง่ายที่จะอัปเดตส่วนหนึ่งของสถานะ โดยขึ้นอยู่กับมูลค่าของสถานะอีกส่วนหนึ่ง สำหรับสิ่งนี้ คุณจะต้องใช้ฮุก useReducer
แทน useState
ในตัวอย่างของเรา เราอาจเห็นสิ่งนี้เมื่ออัปเดต count
ตามค่าของ `move '
บทความเพิ่มเติมจากบล็อก OpenReplay
แบบฟอร์มที่เปิดใช้งานด้วยเสียงใน React with Speechly
เรียนรู้วิธีกรอกแบบฟอร์มด้วยการพูดด้วย React และ Speechly
เผยแพร่ครั้งแรกที่ blog.openreplay.com เมื่อวันที่ 5 พฤษภาคม 2022