อ่านสถานะเริ่มต้นของร้านค้าใน Redux Reducer


85

สถานะเริ่มต้นในแอป Redux สามารถตั้งค่าได้สองวิธี:

หากคุณผ่านสถานะเริ่มต้นไปยังร้านค้าของคุณคุณจะอ่านสถานะนั้นจากร้านค้าได้อย่างไรและทำให้เป็นอาร์กิวเมนต์แรกในตัวลด

คำตอบ:


185

TL; ดร

หากไม่มีcombineReducers()รหัสด้วยตนเองหรือคล้ายกันinitialStateจะชนะstate = ...ในตัวลดเสมอเนื่องจากการstateส่งผ่านไปยังตัวลดคือ initialStateและไม่ใช่ undefinedดังนั้นไวยากรณ์ของอาร์กิวเมนต์ ES6 จึงไม่ถูกนำไปใช้ในกรณีนี้

ด้วยcombineReducers()พฤติกรรมที่เหมาะสมยิ่งขึ้น ตัวลดที่ระบุสถานะinitialStateจะได้รับสิ่งstateนั้น ตัวลดอื่น ๆ จะได้รับundefined และด้วยเหตุนี้จะถอยกลับไปที่state = ...อาร์กิวเมนต์เริ่มต้นที่ระบุ

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

ก่อนอื่นให้พิจารณากรณีที่คุณมีตัวลดเพียงตัวเดียว สมมติว่าคุณไม่ได้ใช้
combineReducers()

จากนั้นตัวลดของคุณอาจมีลักษณะดังนี้:

function counter(state = 0, action) {
  switch (action.type) {
  case 'INCREMENT': return state + 1;
  case 'DECREMENT': return state - 1;
  default: return state;
  }
}

สมมติว่าคุณสร้างร้านค้าด้วยมัน

import { createStore } from 'redux';
let store = createStore(counter);
console.log(store.getState()); // 0

สถานะเริ่มต้นเป็นศูนย์ ทำไม? เพราะอาร์กิวเมนต์ที่สองจะเป็นcreateStore undefinedนี่คือการstateส่งผ่านไปยังตัวลดของคุณในครั้งแรก เมื่อ Redux เริ่มต้นระบบจะส่งการดำเนินการ "จำลอง" เพื่อเติมเต็มสถานะ ดังนั้นตัวcounterลดของคุณจึงถูกเรียกว่าstateเท่ากับundefined. นี่คือกรณีที่ "เปิดใช้งาน" อาร์กิวเมนต์เริ่มต้น ดังนั้นstateตอนนี้0เป็นไปตามค่าเริ่มต้นstate( state = 0) สถานะนี้ ( 0) จะถูกส่งกลับ

ลองพิจารณาสถานการณ์อื่น:

import { createStore } from 'redux';
let store = createStore(counter, 42);
console.log(store.getState()); // 42

ทำไมถึงเป็น42เช่นนี้ไม่ใช่0ครั้งนี้ เพราะcreateStoreถูกเรียกด้วย42เป็นอาร์กิวเมนต์ที่สอง อาร์กิวเมนต์นี้จะstateส่งผ่านไปยังตัวลดของคุณพร้อมกับการดำเนินการจำลอง คราวstateนี้ไม่ได้กำหนด (มัน42!) ดังนั้นไวยากรณ์อาร์กิวเมนต์เริ่มต้น ES6 จึงไม่มีผล stateเป็น42และ42ถูกส่งกลับจากลด


combineReducers()ตอนนี้ขอพิจารณากรณีที่คุณใช้
คุณมีตัวลดสองตัว:

function a(state = 'lol', action) {
  return state;
}

function b(state = 'wat', action) {
  return state;
}

ตัวลดที่สร้างขึ้นโดยcombineReducers({ a, b })มีลักษณะดังนี้:

// const combined = combineReducers({ a, b })
function combined(state = {}, action) {
  return {
    a: a(state.a, action),
    b: b(state.b, action)
  };
}

ถ้าเราเรียกcreateStoreโดยไม่มีinitialStateมันจะเริ่มต้นstateto {}. ดังนั้นstate.aและstate.bจะเป็นไปundefinedตามเวลาที่เรียกaและbตัวลด ทั้งสองaและbreducers จะได้รับundefinedเป็นของพวกเขา stateมีปากเสียงและถ้าพวกเขาระบุค่าเริ่มต้นstateค่าเหล่านั้นจะถูกส่งกลับ นี่คือวิธีที่ตัวลดรวมส่งคืน{ a: 'lol', b: 'wat' }อ็อบเจ็กต์สถานะในการเรียกใช้ครั้งแรก

import { createStore } from 'redux';
let store = createStore(combined);
console.log(store.getState()); // { a: 'lol', b: 'wat' }

ลองพิจารณาสถานการณ์อื่น:

import { createStore } from 'redux';
let store = createStore(combined, { a: 'horse' });
console.log(store.getState()); // { a: 'horse', b: 'wat' }

ตอนนี้ผมระบุเป็นอาร์กิวเมนต์ไปinitialState createStore()สถานะที่ส่งคืนจากตัวลดแบบรวมจะรวมสถานะเริ่มต้นที่ฉันระบุสำหรับตัวaลดกับ'wat'อาร์กิวเมนต์เริ่มต้นที่ระบุว่าตัวbลดเลือกเอง

ลองนึกถึงสิ่งที่ตัวลดรวมทำ:

// const combined = combineReducers({ a, b })
function combined(state = {}, action) {
  return {
    a: a(state.a, action),
    b: b(state.b, action)
  };
}

ในกรณีนี้ถูกระบุดังนั้นจึงไม่ได้ถอยกลับไปstate {}มันเป็นวัตถุที่มีaฟิลด์เท่ากับ'horse'แต่ไม่มีbฟิลด์ นี่คือสาเหตุที่ตัวaลดได้รับ'horse'เป็นของมันstateและยินดีที่จะส่งคืน แต่ตัวbลดได้รับundefinedเป็นของมันstateจึงส่งคืนแนวคิดของค่าเริ่มต้นstate(ในตัวอย่างของเรา'wat') นี่คือวิธีที่เราได้รับ{ a: 'horse', b: 'wat' }ผลตอบแทน


หากต้องการสรุปสิ่งนี้หากคุณยึดติดกับข้อตกลง Redux และคืนสถานะเริ่มต้นจากตัวลดเมื่อถูกเรียกด้วยundefinedเป็นstateอาร์กิวเมนต์ (วิธีที่ง่ายที่สุดในการนำไปใช้คือการระบุstateค่าอาร์กิวเมนต์เริ่มต้น ES6) คุณจะมี พฤติกรรมที่มีประโยชน์ที่ดีสำหรับตัวลดแบบรวม พวกเขาจะชอบค่าที่สอดคล้องกันในinitialStateวัตถุที่คุณส่งผ่านไปยังcreateStore()ฟังก์ชัน แต่ถ้าคุณไม่ผ่านใด ๆ หรือหากไม่ได้ตั้งค่าฟิลด์ที่เกี่ยวข้องstateไว้อาร์กิวเมนต์เริ่มต้นที่ระบุโดยตัวลดจะถูกเลือกแทนวิธีนี้ใช้งานได้ดีเนื่องจากให้ทั้งการเริ่มต้นและการให้ความชุ่มชื้นของข้อมูลที่มีอยู่ แต่ช่วยให้ตัวลดแต่ละตัวรีเซ็ตสถานะได้หากข้อมูลไม่ได้รับการเก็บรักษา แน่นอนว่าคุณสามารถใช้รูปแบบนี้ซ้ำได้เนื่องจากคุณสามารถใช้combineReducers()ในหลายระดับหรือแม้แต่เขียนตัวลดด้วยตนเองโดยการเรียกตัวลดขนาดและให้เป็นส่วนที่เกี่ยวข้องของแผนผังสถานะ


3
ขอบคุณสำหรับรายละเอียดที่ดี - สิ่งที่ฉันกำลังมองหา ความยืดหยุ่นของ API ของคุณยอดเยี่ยมมาก ส่วนที่ผมไม่ได้เห็นก็คือ "เด็ก" combineReducersลดจะเข้าใจซึ่งชิ้นส่วนของสถานะเริ่มต้นเป็นของมันอยู่บนพื้นฐานที่สำคัญที่ใช้ในการ ขอบคุณอีกครั้งมาก ๆ
cantera

ขอบคุณสำหรับคำตอบนี้ Dan ถ้าฉันย่อยคำตอบของคุณถูกต้องคุณกำลังบอกว่าควรตั้งค่าสถานะเริ่มต้นผ่านประเภทการทำงานของตัวลด ตัวอย่างเช่น GET_INITIAL_STATE และเรียกการจัดส่งไปยังประเภทการดำเนินการนั้นในสมมติว่าการเรียกกลับ componentDidMount?
Con Antonakos

@ConAntonakos ไม่ฉันไม่ได้หมายถึงสิ่งนี้ ผมหมายถึงการเลือกที่เหมาะสมสถานะเริ่มต้นที่คุณกำหนดลดเช่นหรือfunction counter(state = 0, action) function visibleIds(state = [], action)
Dan Abramov

1
@DanAbramov: การใช้อาร์กิวเมนต์ที่สองใน createstore () ฉันจะคืนสถานะเมื่อโหลดเพจ SPA ซ้ำได้อย่างไรด้วยสถานะที่จัดเก็บล่าสุดใน Redux แทนที่จะเป็นค่า intial ฉันเดาว่าฉันกำลังถามว่าฉันจะแคชทั้งร้านได้อย่างไรและเก็บรักษาไว้ในการโหลดซ้ำและส่งต่อไปยังฟังก์ชัน createstore
jasan

1
@jasan: ใช้ localStorage egghead.io/lessons/…
Dan Abramov

5

โดยสรุป: Redux เป็นผู้ที่ส่งผ่านสถานะเริ่มต้นไปยังตัวลดคุณไม่จำเป็นต้องทำอะไรเลย

เมื่อคุณโทรหาcreateStore(reducer, [initialState])คุณจะแจ้งให้ Redux ทราบว่าสถานะเริ่มต้นที่จะส่งผ่านไปยังตัวลดคืออะไรเมื่อมีการดำเนินการครั้งแรก

ตัวเลือกที่สองที่คุณพูดถึงจะใช้เฉพาะในกรณีที่คุณไม่ผ่านสถานะเริ่มต้นเมื่อสร้างร้านค้า กล่าวคือ

function todoApp(state = initialState, action)

สถานะจะเริ่มต้นก็ต่อเมื่อไม่มีสถานะผ่าน Redux


1
ขอบคุณสำหรับการตอบกลับ - ในกรณีขององค์ประกอบตัวลดหากคุณใช้สถานะเริ่มต้นกับร้านค้าคุณจะบอกตัวลดลูก / ตัวลดย่อยได้อย่างไรว่าเป็นส่วนใดของโครงสร้างสถานะเริ่มต้น ตัวอย่างเช่นหากคุณมีmenuStateฉันจะบอกให้ตัวลดเมนูอ่านค่าstate.menuจากร้านค้าและใช้ค่านั้นเป็นสถานะเริ่มต้นได้อย่างไร
cantera

ขอบคุณ แต่คำถามของฉันต้องไม่ชัดเจน ฉันจะรอดูว่าคนอื่นตอบกลับก่อนแก้ไขไหม
cantera

1

คุณอ่านสถานะนั้นจากร้านค้าอย่างไรและทำให้เป็นอาร์กิวเมนต์แรกในตัวลดขนาดของคุณได้อย่างไร

CombinedReducers () ทำงานให้คุณ วิธีแรกในการเขียนมันไม่ได้เป็นประโยชน์จริงๆ:

const rootReducer = combineReducers({ todos, users })

แต่อีกอันที่เทียบเท่านั้นชัดเจนกว่า:

function rootReducer(state, action) {
   todos: todos(state.todos, action),
   users: users(state.users, action)
}

0

ฉันหวังว่านี่จะตอบสนองคำขอของคุณ (ซึ่งฉันเข้าใจว่าเป็นการเริ่มต้นตัวลดในขณะที่ผ่าน intialState และส่งคืนสถานะนั้น)

นี่คือวิธีที่เราทำ (คำเตือน: คัดลอกจากรหัส typescript)

ส่วนสำคัญของมันคือการif(!state)ทดสอบในฟังก์ชัน mainReducer (โรงงาน)

function getInitialState(): MainState {

     return {
         prop1:                 'value1',
         prop1:                 'value2',
         ...        
     }
}



const reducer = combineReducers(
    {
        main:     mainReducer( getInitialState() ),
        ...
    }
)



const mainReducer = ( initialState: MainState ): Reducer => {

    return ( state: MainState, action: Action ): MainState => {

        if ( !state ) {
            return initialState
        }

        console.log( 'Main reducer action: ', action ) 

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