อย่าตกอยู่ในกับดักของการคิดห้องสมุดควรกำหนดวิธีการที่จะทำทุกอย่าง ถ้าคุณต้องการที่จะทำอะไรกับหมดเวลาใน JavaScript setTimeout
ที่คุณจำเป็นต้องใช้ ไม่มีเหตุผลใดที่การกระทำของ Redux ควรจะแตกต่างกัน
Redux จะนำเสนอทางเลือกบางส่วนของการจัดการกับสิ่งที่ไม่ตรงกัน แต่คุณควรใช้เหล่านั้นเมื่อคุณรู้ว่าคุณมีการทำซ้ำรหัสมากเกินไป หากคุณไม่มีปัญหานี้ให้ใช้สิ่งที่ภาษาเสนอและหาทางออกที่ง่ายที่สุด
การเขียน Async Code Inline
นี่เป็นวิธีที่ง่ายที่สุด และไม่มีอะไรพิเศษสำหรับ Redux ที่นี่
store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
store.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)
ในทำนองเดียวกันจากภายในองค์ประกอบที่เชื่อมต่อ:
this.props.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
this.props.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)
ความแตกต่างเพียงอย่างเดียวก็คือในองค์ประกอบที่เชื่อมต่อคุณมักจะไม่สามารถเข้าถึงร้านค้าได้ แต่จะได้รับdispatch()
การฉีดยาอย่างใดอย่างหนึ่งหรือโดยเฉพาะอย่างยิ่งผู้สร้างแอ็คชั่น อย่างไรก็ตามสิ่งนี้ไม่ได้สร้างความแตกต่างให้เรา
หากคุณไม่ชอบการพิมพ์ผิดเมื่อส่งการกระทำเดียวกันจากส่วนประกอบที่แตกต่างกันคุณอาจต้องการแยกผู้สร้างแอ็คชันแทนการมอบหมายภารกิจการกระทำในแบบอินไลน์:
// actions.js
export function showNotification(text) {
return { type: 'SHOW_NOTIFICATION', text }
}
export function hideNotification() {
return { type: 'HIDE_NOTIFICATION' }
}
// component.js
import { showNotification, hideNotification } from '../actions'
this.props.dispatch(showNotification('You just logged in.'))
setTimeout(() => {
this.props.dispatch(hideNotification())
}, 5000)
หรือหากคุณเคยผูกมัดไว้กับconnect()
:
this.props.showNotification('You just logged in.')
setTimeout(() => {
this.props.hideNotification()
}, 5000)
จนถึงตอนนี้เรายังไม่ได้ใช้มิดเดิลแวร์หรือแนวคิดขั้นสูงอื่น ๆ
แยก Async Action Creator
วิธีการดังกล่าวใช้งานได้ดีในกรณีง่าย ๆ แต่คุณอาจพบว่ามีปัญหาเล็กน้อย:
- มันบังคับให้คุณทำซ้ำตรรกะนี้ทุกที่ที่คุณต้องการแสดงการแจ้งเตือน
- การแจ้งเตือนไม่มี ID ดังนั้นคุณจะมีสภาพการแข่งขันหากคุณแสดงการแจ้งเตือนสองรายการเร็วพอ เมื่อการหมดเวลาครั้งแรกเสร็จสิ้นจะมีการจัดส่ง
HIDE_NOTIFICATION
ซ่อนการแจ้งเตือนครั้งที่สองโดยเร็วกว่าการหมดเวลา
ในการแก้ปัญหาเหล่านี้คุณจะต้องแยกฟังก์ชั่นที่รวมศูนย์ตรรกะการหมดเวลาและส่งการกระทำทั้งสอง อาจมีลักษณะเช่นนี้:
// actions.js
function showNotification(id, text) {
return { type: 'SHOW_NOTIFICATION', id, text }
}
function hideNotification(id) {
return { type: 'HIDE_NOTIFICATION', id }
}
let nextNotificationId = 0
export function showNotificationWithTimeout(dispatch, text) {
// Assigning IDs to notifications lets reducer ignore HIDE_NOTIFICATION
// for the notification that is not currently visible.
// Alternatively, we could store the timeout ID and call
// clearTimeout(), but we’d still want to do it in a single place.
const id = nextNotificationId++
dispatch(showNotification(id, text))
setTimeout(() => {
dispatch(hideNotification(id))
}, 5000)
}
ขณะนี้ส่วนประกอบสามารถใช้งานได้showNotificationWithTimeout
โดยไม่ต้องทำซ้ำตรรกะนี้หรือมีสภาพการแข่งขันพร้อมการแจ้งเตือนที่แตกต่างกัน:
// component.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')
// otherComponent.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged out.')
เหตุใดจึงshowNotificationWithTimeout()
ยอมรับdispatch
ว่าเป็นอาร์กิวเมนต์แรก เพราะมันต้องส่งการกระทำไปที่ร้าน โดยปกติส่วนประกอบมีการเข้าถึงdispatch
แต่เนื่องจากเราต้องการให้ฟังก์ชั่นภายนอกสามารถควบคุมการส่งข้อมูลได้เราจำเป็นต้องให้มันควบคุมการส่งข้อมูล
หากคุณมีร้านค้าแบบซิงเกิลตันที่ส่งออกจากโมดูลบางตัวคุณสามารถนำเข้าและนำเข้าdispatch
โดยตรงได้ที่:
// store.js
export default createStore(reducer)
// actions.js
import store from './store'
// ...
let nextNotificationId = 0
export function showNotificationWithTimeout(text) {
const id = nextNotificationId++
store.dispatch(showNotification(id, text))
setTimeout(() => {
store.dispatch(hideNotification(id))
}, 5000)
}
// component.js
showNotificationWithTimeout('You just logged in.')
// otherComponent.js
showNotificationWithTimeout('You just logged out.')
สิ่งนี้ดูง่ายกว่า แต่เราไม่แนะนำวิธีนี้ เหตุผลหลักที่เราไม่ชอบก็คงเป็นเพราะมันบังคับให้ร้านค้าที่จะเป็นเดี่ยว นี้จะทำให้มันยากมากที่จะใช้การแสดงผลเซิร์ฟเวอร์ บนเซิร์ฟเวอร์คุณจะต้องการให้แต่ละคำขอมีร้านค้าของตัวเองเพื่อให้ผู้ใช้ที่แตกต่างกันได้รับข้อมูลที่โหลดไว้ล่วงหน้าที่แตกต่างกัน
ร้านเดี่ยวยังทำให้การทดสอบยากขึ้น คุณไม่สามารถจำลองร้านค้าได้อีกต่อไปเมื่อทำการทดสอบผู้สร้างแอ็คชันเนื่องจากพวกเขาอ้างอิงร้านค้าจริงที่ส่งออกจากโมดูลเฉพาะ คุณไม่สามารถรีเซ็ตสถานะจากภายนอกได้
ดังนั้นในขณะที่คุณสามารถส่งออกที่เก็บซิงเกิลจากโมดูลได้ในทางเทคนิคเราไม่แนะนำให้ใช้ อย่าทำสิ่งนี้จนกว่าคุณจะแน่ใจว่าแอปของคุณจะไม่เพิ่มการแสดงผลเซิร์ฟเวอร์
กลับไปเป็นรุ่นก่อนหน้า:
// actions.js
// ...
let nextNotificationId = 0
export function showNotificationWithTimeout(dispatch, text) {
const id = nextNotificationId++
dispatch(showNotification(id, text))
setTimeout(() => {
dispatch(hideNotification(id))
}, 5000)
}
// component.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')
// otherComponent.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged out.')
วิธีนี้จะช่วยแก้ปัญหาด้วยการทำซ้ำของตรรกะและช่วยเราจากสภาวะการแข่งขัน
Thunk Middleware
สำหรับแอพที่ใช้งานง่ายวิธีการก็น่าจะพอเพียง ไม่ต้องกังวลกับมิดเดิลแวร์ถ้าคุณพอใจกับมัน
อย่างไรก็ตามในแอปขนาดใหญ่คุณอาจพบว่ามีความไม่สะดวกอยู่รอบตัว
ตัวอย่างเช่นดูเหมือนว่าโชคร้ายที่เราต้องผ่านdispatch
ไป สิ่งนี้ทำให้เป็นการยากที่จะแยกส่วนประกอบของตู้คอนเทนเนอร์และอุปกรณ์นำเสนอเนื่องจากส่วนประกอบใด ๆ ที่แจกจ่ายแอ็คชั่น Redux แบบอะซิงโครนัสในลักษณะข้างต้นต้องยอมรับdispatch
ว่าเป็นอุปกรณ์ประกอบฉากเพื่อให้สามารถส่งต่อได้ คุณไม่สามารถผูกมัดผู้สร้างแอคชั่นได้connect()
อีกต่อไปเพราะshowNotificationWithTimeout()
ไม่ใช่ผู้สร้างแอ็คชั่นจริงๆ มันไม่ส่งคืนแอคชัน Redux
นอกจากนี้ยังสามารถเป็นที่น่าอึดอัดใจที่จะจำซึ่งทำหน้าที่เป็นผู้สร้างการดำเนินการซิงโครเหมือนและที่ผู้ช่วยเหลือไม่ตรงกันเช่นshowNotification()
showNotificationWithTimeout()
คุณต้องใช้มันต่างกันและระวังไม่ให้เข้าใจผิดกับกันและกัน
นี่คือแรงจูงใจในการค้นหาวิธีการ“ ทำให้ถูกต้องตามกฎหมาย” รูปแบบของการให้dispatch
ฟังก์ชั่นผู้ช่วยนี้และช่วย Redux“ ดู” ผู้สร้างแอ็คชั่นอะซิงโครนัสเช่นกรณีพิเศษของผู้สร้างแอ็คชั่นทั่วไป
หากคุณยังอยู่กับเราและคุณก็รู้ว่าเป็นปัญหาในแอปของคุณคุณสามารถใช้มิดเดิลแวร์Redux Thunkได้
สรุปสาระสำคัญ Redux Thunk สอนให้ Redux รู้จักการกระทำแบบพิเศษที่มีอยู่ในฟังก์ชั่นจริง:
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
const store = createStore(
reducer,
applyMiddleware(thunk)
)
// It still recognizes plain object actions
store.dispatch({ type: 'INCREMENT' })
// But with thunk middleware, it also recognizes functions
store.dispatch(function (dispatch) {
// ... which themselves may dispatch many times
dispatch({ type: 'INCREMENT' })
dispatch({ type: 'INCREMENT' })
dispatch({ type: 'INCREMENT' })
setTimeout(() => {
// ... even asynchronously!
dispatch({ type: 'DECREMENT' })
}, 1000)
})
เมื่อเปิดใช้งานมิดเดิลแวร์นี้หากคุณส่งมอบฟังก์ชันมิดเดิ้ล Redux Thunk จะมอบให้dispatch
เป็นอาร์กิวเมนต์ นอกจากนี้ยังจะ "กลืน" การกระทำดังกล่าวดังนั้นจึงไม่ต้องกังวลกับอุปกรณ์ลดขนาดของคุณที่รับฟังก์ชั่นที่แปลก อุปกรณ์ลดแรงกระแทกของคุณจะได้รับการกระทำวัตถุธรรมดาเท่านั้นไม่ว่าจะปล่อยโดยตรงหรือปล่อยออกมาจากฟังก์ชั่นตามที่เราเพิ่งอธิบาย
มันไม่ได้ดูมีประโยชน์มากใช่ไหม? ไม่ได้อยู่ในสถานการณ์นี้โดยเฉพาะ อย่างไรก็ตามมันช่วยให้เราประกาศshowNotificationWithTimeout()
เป็น Redux action creator:
// actions.js
function showNotification(id, text) {
return { type: 'SHOW_NOTIFICATION', id, text }
}
function hideNotification(id) {
return { type: 'HIDE_NOTIFICATION', id }
}
let nextNotificationId = 0
export function showNotificationWithTimeout(text) {
return function (dispatch) {
const id = nextNotificationId++
dispatch(showNotification(id, text))
setTimeout(() => {
dispatch(hideNotification(id))
}, 5000)
}
}
โปรดทราบว่าฟังก์ชั่นนั้นเกือบจะเหมือนกับฟังก์ชันที่เราเขียนในส่วนก่อนหน้า อย่างไรก็ตามมันไม่ยอมรับdispatch
ว่าเป็นอาร์กิวเมนต์แรก แต่จะส่งคืนฟังก์ชันที่ยอมรับdispatch
ว่าเป็นอาร์กิวเมนต์แรก
เราจะใช้มันอย่างไรในองค์ประกอบของเรา แน่นอนเราสามารถเขียนสิ่งนี้:
// component.js
showNotificationWithTimeout('You just logged in.')(this.props.dispatch)
เราจะเรียกร้องให้ผู้สร้างการกระทำ async ที่จะได้รับฟังก์ชั่นภายในที่ต้องการเพียงและจากนั้นเราจะผ่านdispatch
dispatch
อย่างไรก็ตามนี่มันน่าอึดอัดใจกว่าเวอร์ชั่นดั้งเดิมมาก! ทำไมเราถึงไปทางนั้น
เพราะสิ่งที่ฉันบอกคุณก่อน หาก Redux Thunk มิดเดิลแวร์ถูกเปิดใช้งานทุกครั้งที่คุณพยายามที่จะส่งฟังก์ชั่นแทนวัตถุกระทำตัวกลางจะเรียกฟังก์ชั่นที่มีdispatch
วิธีการของตัวเองเป็นอาร์กิวเมนต์แรก
ดังนั้นเราสามารถทำสิ่งนี้แทน:
// component.js
this.props.dispatch(showNotificationWithTimeout('You just logged in.'))
ในที่สุดการกระจายการกระทำแบบอะซิงโครนัส (จริงๆแล้วชุดของการกระทำ) ดูไม่แตกต่างจากการจัดส่งการกระทำเดียวพร้อมกันกับองค์ประกอบ ซึ่งเป็นสิ่งที่ดีเพราะส่วนประกอบไม่ควรสนใจว่ามีบางอย่างเกิดขึ้นพร้อมกันหรือไม่พร้อมกัน เราแยกออกไปว่า
โปรดสังเกตว่าเนื่องจากเรา“ สอน” ให้ Redux รู้จักผู้สร้างแอ็คชั่น“ พิเศษ” (เราเรียกพวกเขาว่าผู้สร้างแอ็คชั่นอันธพาล ) ตอนนี้เราสามารถใช้พวกมันได้ทุกที่ที่เราจะใช้ผู้สร้างแอ็คชั่นทั่วไป ตัวอย่างเช่นเราสามารถใช้พวกเขากับconnect()
:
// actions.js
function showNotification(id, text) {
return { type: 'SHOW_NOTIFICATION', id, text }
}
function hideNotification(id) {
return { type: 'HIDE_NOTIFICATION', id }
}
let nextNotificationId = 0
export function showNotificationWithTimeout(text) {
return function (dispatch) {
const id = nextNotificationId++
dispatch(showNotification(id, text))
setTimeout(() => {
dispatch(hideNotification(id))
}, 5000)
}
}
// component.js
import { connect } from 'react-redux'
// ...
this.props.showNotificationWithTimeout('You just logged in.')
// ...
export default connect(
mapStateToProps,
{ showNotificationWithTimeout }
)(MyComponent)
อ่านรัฐใน Thunks
โดยปกติ reducers ของคุณจะมีตรรกะทางธุรกิจสำหรับการกำหนดสถานะต่อไป อย่างไรก็ตาม reducers จะเตะเข้าหลังจากการกระทำถูกส่งออกไป จะเป็นอย่างไรถ้าคุณมีผลข้างเคียง (เช่นเรียก API) ในผู้สร้างแอ็คชั่น thunk และคุณต้องการป้องกันมันภายใต้เงื่อนไขบางอย่าง?
โดยไม่ใช้มิดเดิลแวร์ thunk คุณเพียงแค่ทำเครื่องหมายนี้ในส่วนประกอบ:
// component.js
if (this.props.areNotificationsEnabled) {
showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')
}
อย่างไรก็ตามจุดของการแยกผู้สร้างแอ็คชั่นคือการรวมตรรกะซ้ำ ๆ นี้ไว้ที่ส่วนกลางในหลาย ๆ องค์ประกอบ โชคดีที่ Redux Thunk เสนอวิธีการอ่านสถานะปัจจุบันของร้าน Redux นอกจากนี้dispatch
มันยังผ่านgetState
เป็นอาร์กิวเมนต์ที่สองไปยังฟังก์ชั่นที่คุณกลับมาจากผู้สร้างการกระทำอันธพาลของคุณ ซึ่งทำให้ thunk อ่านสถานะปัจจุบันของร้านค้า
let nextNotificationId = 0
export function showNotificationWithTimeout(text) {
return function (dispatch, getState) {
// Unlike in a regular action creator, we can exit early in a thunk
// Redux doesn’t care about its return value (or lack of it)
if (!getState().areNotificationsEnabled) {
return
}
const id = nextNotificationId++
dispatch(showNotification(id, text))
setTimeout(() => {
dispatch(hideNotification(id))
}, 5000)
}
}
อย่าใช้รูปแบบนี้ในทางที่ผิด เป็นการดีสำหรับการประกันตัวจากการเรียกใช้ API เมื่อมีข้อมูลแคชอยู่ แต่ไม่ได้เป็นรากฐานที่ดีในการสร้างตรรกะทางธุรกิจของคุณ หากคุณใช้getState()
เพื่อส่งการกระทำที่แตกต่างตามเงื่อนไขให้พิจารณาวางตรรกะทางธุรกิจลงในตัวลดแทน
ขั้นตอนถัดไป
ตอนนี้คุณมีสัญชาตญาณพื้นฐานเกี่ยวกับวิธีการทำงานของ Thunks ลองดูตัวอย่าง Redux asyncที่ใช้พวกมัน
คุณอาจพบตัวอย่างมากมายที่ thunks ส่งคืนสัญญา สิ่งนี้ไม่จำเป็น แต่สะดวกมาก Redux ไม่สนใจสิ่งที่คุณกลับมาจาก thunk dispatch()
แต่มันช่วยให้คุณมีค่าตอบแทนจาก นี่คือเหตุผลที่คุณสามารถกลับสัญญาจาก thunk dispatch(someThunkReturningPromise()).then(...)
และรอให้เสร็จสมบูรณ์โดยการโทร
คุณยังสามารถแบ่งผู้สร้างแอ็คชั่นที่ซับซ้อนออกเป็นผู้สร้างแอ็คชั่นที่มีขนาดเล็กลงหลายคน dispatch
วิธีการให้บริการโดย thunks สามารถยอมรับ thunks ตัวเองเพื่อให้คุณสามารถใช้รูปแบบซ้ำ อีกครั้งสิ่งนี้ทำงานได้ดีที่สุดกับ Promises เพราะคุณสามารถนำโฟลว์การควบคุมแบบอะซิงโครนัสมาใช้งานได้
สำหรับแอพบางตัวคุณอาจพบว่าตัวเองอยู่ในสถานการณ์ที่ความต้องการการควบคุมแบบอะซิงโครนัสของคุณซับซ้อนเกินไปที่จะแสดงด้วย thunks ตัวอย่างเช่นการลองคำขอที่ล้มเหลวการอนุญาตให้ใช้ซ้ำด้วยโทเค็นหรือออนบอร์ดแบบทีละขั้นตอนอาจมีความละเอียดมากเกินไปและเกิดข้อผิดพลาดได้ง่ายเมื่อเขียนด้วยวิธีนี้ ในกรณีนี้คุณอาจต้องการที่จะมองไปที่การแก้ปัญหาการควบคุมการไหลตรงกันที่สูงขึ้นเช่นRedux SagaหรือRedux ห่วง ประเมินตัวอย่างเปรียบเทียบตัวอย่างที่เกี่ยวข้องกับความต้องการของคุณและเลือกตัวอย่างที่คุณชอบมากที่สุด
สุดท้ายอย่าใช้อะไร (รวมถึง thunks) หากคุณไม่มีความต้องการที่แท้จริงสำหรับพวกเขา โปรดจำไว้ว่าขึ้นอยู่กับข้อกำหนดโซลูชันของคุณอาจดูเรียบง่าย
store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
store.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)
อย่าเหงื่อมันจนกว่าคุณจะรู้ว่าทำไมคุณทำเช่นนี้
redux-saga
คำตอบของฉันหากคุณต้องการสิ่งที่ดีกว่า thunks คำตอบล่าช้าดังนั้นคุณต้องเลื่อนเป็นเวลานานก่อนที่จะเห็นมันปรากฏ :) ไม่ได้หมายความว่ามันไม่คุ้มค่าที่จะอ่าน นี่คือทางลัด: stackoverflow.com/a/38574266/82609