การเข้าถึงสถานะ Redux ในตัวสร้างแอ็คชันหรือไม่


296

พูดว่าฉันมีดังต่อไปนี้:

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return {
    type: SOME_ACTION,
  }
}

และในผู้สร้างการกระทำนั้นฉันต้องการเข้าถึงสถานะร้านค้าทั่วโลก (ตัวลดทั้งหมด) มันจะดีกว่าถ้าทำสิ่งนี้:

import store from '../store';

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return {
    type: SOME_ACTION,
    items: store.getState().otherReducer.items,
  }
}

หรือสิ่งนี้:

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return (dispatch, getState) => {
    const {items} = getState().otherReducer;

    dispatch(anotherAction(items));
  }
}

คำตอบ:


522

มีความคิดเห็นที่แตกต่างกันว่าการเข้าถึงสถานะในผู้สร้างแอ็คชั่นเป็นความคิดที่ดีหรือไม่:

  • Dan Abramov ผู้สร้าง Redux รู้สึกว่าควร จำกัด : "กรณีการใช้งานไม่กี่กรณีที่ฉันคิดว่าเป็นเรื่องที่ยอมรับได้คือการตรวจสอบข้อมูลแคชก่อนที่คุณจะทำการร้องขอหรือตรวจสอบว่าคุณได้รับการรับรองความถูกต้องหรือไม่ ฉันคิดว่าการส่งผ่านข้อมูลเช่นstate.something.itemsในผู้สร้างแอ็คชั่นนั้นเป็นรูปแบบการต่อต้านและไม่สนับสนุนเพราะมันบดบังประวัติการเปลี่ยนแปลง: หากมีข้อผิดพลาดและitemsไม่ถูกต้องมันยากที่จะติดตามว่าค่าที่ไม่ถูกต้องเหล่านั้นมาจากอะไร เป็นส่วนหนึ่งของการกระทำแทนที่จะคำนวณโดยตรงโดยตัวลดในการตอบสนองต่อการกระทำดังนั้นจงทำสิ่งนี้ด้วยความระมัดระวัง "
  • Redux ปัจจุบันดูแลมาร์คอีริคสันกล่าวว่ามันปรับและแม้กระทั่งการสนับสนุนให้ใช้getStateใน thunks - ที่ว่าทำไมมันมีอยู่ เขากล่าวถึงข้อดีและข้อเสียในการเข้าถึงของรัฐในการดำเนินการในผู้สร้างบล็อกของเขาโพสต์สำนวน Redux: ความคิดเกี่ยวกับ Thunks, โศกนาฏกรรม, นามธรรมและสามารถนำมาใช้

หากคุณพบว่าคุณต้องการสิ่งนี้แนวทางทั้งสองที่คุณแนะนำนั้นใช้ได้ วิธีแรกไม่ต้องการมิดเดิลแวร์ใด ๆ :

import store from '../store';

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return {
    type: SOME_ACTION,
    items: store.getState().otherReducer.items,
  }
}

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

นี่คือเหตุผลที่เราแนะนำวิธีที่สอง:

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return (dispatch, getState) => {
    const {items} = getState().otherReducer;

    dispatch(anotherAction(items));
  }
}

มันจะต้องให้คุณใช้มิดเดิลแวร์ Redux Thunk แต่ก็ใช้งานได้ดีทั้งบนไคลเอนต์และบนเซิร์ฟเวอร์ คุณสามารถอ่านเพิ่มเติมเกี่ยวกับ Redux Thunk และทำไมมันจำเป็นในกรณีนี้ที่นี่

เป็นการดีที่การกระทำของคุณไม่ควร“ อ้วน” และควรมีข้อมูลน้อยที่สุดเท่าที่จะเป็นไปได้ แต่คุณควรทำสิ่งที่ดีที่สุดสำหรับคุณในใบสมัครของคุณเอง คำถามที่พบบ่อย Redux มีข้อมูลเกี่ยวกับการแยกลอจิกระหว่างผู้สร้างแอ็คชั่นและตัวลดและเวลาที่อาจเป็นประโยชน์ในการใช้getStateในผู้สร้างแอคชั่


5
ฉันมีสถานการณ์หนึ่งเมื่อเลือกบางอย่างในส่วนประกอบอาจทำให้เกิด PUT หรือ POST ขึ้นอยู่กับว่าที่เก็บมีข้อมูลที่เกี่ยวข้องกับส่วนประกอบหรือไม่ มันจะดีกว่าที่จะวางตรรกะทางธุรกิจสำหรับการเลือก PUT / POST ในองค์ประกอบแทนผู้สร้างการกระทำที่ใช้ thunk?
vicusbass

3
การปฏิบัติที่ดีที่สุดคืออะไร? ตอนนี้ฉันกำลังเผชิญกับปัญหาที่คล้ายกันซึ่งฉันใช้ getState ในผู้สร้างแอ็คชั่นของฉัน ในกรณีของฉันฉันใช้เพื่อพิจารณาว่าค่าหากแบบฟอร์มมีการเปลี่ยนแปลงที่รอดำเนินการ (และถ้าเป็นเช่นนั้นฉันจะส่งการกระทำที่แสดงกล่องโต้ตอบ)
Arne H. Bitubekk

42
ไม่เป็นไรที่จะอ่านจากร้านค้าในผู้สร้างแอคชั่น ฉันขอแนะนำให้คุณใช้ตัวเลือกเพื่อให้คุณไม่ได้ขึ้นอยู่กับรูปร่างของรัฐที่แน่นอน
Dan Abramov

2
ฉันใช้มิดเดิลแวร์เพื่อส่งข้อมูลไปยังมิกซ์พาเนล ดังนั้นฉันจึงมีเมตาคีย์ในแอคชั่น ฉันต้องผ่านตัวแปรต่าง ๆ จากรัฐไปยังแผงผสม การตั้งค่าให้กับผู้สร้างแอ็คชั่นน่าจะเป็นรูปแบบต่อต้าน อะไรจะเป็นวิธีที่ดีที่สุดในการจัดการกรณีการใช้งานประเภทนี้?
Aakash Sigdel

2
โอ้! ฉันไม่ได้โบว์ที่ redux thunk ได้รับgetStateเป็นพารามิเตอร์ตัวที่สองฉัน
ทุบ

33

เมื่อสถานการณ์ของคุณง่ายคุณสามารถใช้

import store from '../store';

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return {
    type: SOME_ACTION,
    items: store.getState().otherReducer.items,
  }
}

แต่บางครั้งคุณก็action creatorต้องการกระตุ้นการทำงานหลายอย่าง

เช่นคำขอ async ดังนั้นคุณต้อง REQUEST_LOAD REQUEST_LOAD_SUCCESS REQUEST_LOAD_FAILดำเนินการ

export const [REQUEST_LOAD, REQUEST_LOAD_SUCCESS, REQUEST_LOAD_FAIL] = [`REQUEST_LOAD`
    `REQUEST_LOAD_SUCCESS`
    `REQUEST_LOAD_FAIL`
]
export function someAction() {
    return (dispatch, getState) => {
        const {
            items
        } = getState().otherReducer;
        dispatch({
            type: REQUEST_LOAD,
            loading: true
        });
        $.ajax('url', {
            success: (data) => {
                dispatch({
                    type: REQUEST_LOAD_SUCCESS,
                    loading: false,
                    data: data
                });
            },
            error: (error) => {
                dispatch({
                    type: REQUEST_LOAD_FAIL,
                    loading: false,
                    error: error
                });
            }
        })
    }
}

หมายเหตุ:คุณต้องการredux-thunkเพื่อส่งคืนฟังก์ชันใน action creator


3
ฉันสามารถถามได้ไหมว่าควรตรวจสอบสถานะของสถานะ 'กำลังโหลด' เพื่อไม่ให้ทำการร้องขอ ajax อื่นในขณะที่คำขอแรกจบหรือไม่
JoeTidee

@JoeTidee ในตัวอย่างการส่งสถานะการโหลดเสร็จสิ้น หากคุณดำเนินการนี้ด้วยปุ่มคุณจะตรวจสอบว่าloading === trueมีและปิดใช้งานปุ่มหรือไม่
croraf

4

ฉันเห็นด้วยกับ @Bloomca การส่งค่าที่ต้องการจากร้านค้าไปยังฟังก์ชั่นการจัดส่งเนื่องจากอาร์กิวเมนต์ดูเหมือนง่ายกว่าการส่งออกร้านค้า ฉันทำตัวอย่างที่นี่:

import React from "react";
import {connect} from "react-redux";
import * as actions from '../actions';

class App extends React.Component {

  handleClick(){
    const data = this.props.someStateObject.data;
    this.props.someDispatchFunction(data);
  }

  render(){
    return (
      <div>       
      <div onClick={ this.handleClick.bind(this)}>Click Me!</div>      
      </div>
    );
  }
}


const mapStateToProps = (state) => {
  return { someStateObject: state.someStateObject };
};

const mapDispatchToProps = (dispatch) => {
  return {
    someDispatchFunction:(data) => { dispatch(actions.someDispatchFunction(data))},

  };
}


export default connect(mapStateToProps, mapDispatchToProps)(App);

วิธีนี้ค่อนข้างสมเหตุสมผล
Ahmet Şimşek

นี่เป็นวิธีที่ถูกต้องที่จะทำและวิธีที่ฉันทำ ผู้สร้างแอ็คชั่นของคุณไม่จำเป็นต้องรู้ทั้งรัฐเพียงเล็กน้อยเท่านั้นที่เกี่ยวข้อง
แอช

3

ฉันอยากจะชี้ให้เห็นว่ามันไม่เลวร้ายที่จะอ่านจากร้านค้า - มันอาจจะสะดวกกว่าที่จะตัดสินใจว่าควรทำอะไรจากร้านค้าแทนที่จะส่งทุกอย่างไปยังส่วนประกอบแล้วเป็นพารามิเตอร์ของ ฟังก์ชั่น ฉันเห็นด้วยกับ Dan อย่างสมบูรณ์ว่าเป็นการดีกว่าที่จะไม่ใช้ store เป็น singletone เว้นแต่คุณจะแน่ใจ 100% ว่าคุณจะใช้สำหรับการเรนเดอร์ฝั่งไคลเอ็นต์เท่านั้น

เมื่อเร็ว ๆ นี้ฉันได้สร้างห้องสมุดเพื่อจัดการกับความฟุ้งซ่านของ redux และฉันคิดว่ามันเป็นความคิดที่ดีที่จะใส่ทุกอย่างไว้ในมิดเดิลแวร์

ดังนั้นตัวอย่างของคุณจะเป็นดังนี้:

import { createSyncTile } from 'redux-tiles';

const someTile = createSyncTile({
  type: ['some', 'tile'],
  fn: ({ params, selectors, getState }) => {
    return {
      data: params.data,
      items: selectors.another.tile(getState())
    };
  },
});

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


1

นำเสนอทางเลือกในการแก้ปัญหานี้ นี่อาจจะดีกว่าหรือแย่กว่าโซลูชันของ Dan ขึ้นอยู่กับแอปพลิเคชันของคุณ

คุณสามารถรับสถานะจาก reducers ไปสู่การกระทำได้โดยแบ่งการกระทำออกเป็น 2 ฟังก์ชั่นแยกกัน: ก่อนอื่นขอข้อมูลทำหน้าที่ที่สองกับข้อมูล redux-loopคุณสามารถทำได้โดยใช้

ก่อน 'กรุณาขอข้อมูล'

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
    return {
        type: SOME_ACTION,
    }
}

redux-loopในการลดการตัดถามและให้ข้อมูลเพื่อการดำเนินการขั้นตอนที่สองโดยใช้

import { loop, Cmd } from 'redux-loop';
const initialState = { data: '' }
export default (state=initialState, action) => {
    switch(action.type) {
        case SOME_ACTION: {
            return loop(state, Cmd.action(anotherAction(state.data))
        }
    }
}

ด้วยข้อมูลในมือให้ทำสิ่งที่คุณต้องการในตอนแรก

export const ANOTHER_ACTION = 'ANOTHER_ACTION';
export function anotherAction(data) {
    return {
        type: ANOTHER_ACTION,
        payload: data,
    }
}

หวังว่านี่จะช่วยใครซักคน


0

ฉันรู้ว่าฉันมางานปาร์ตี้ที่นี่ช้า แต่ฉันมาที่นี่เพื่อแสดงความคิดเห็นเกี่ยวกับความปรารถนาของฉันที่จะใช้สถานะในการกระทำและจากนั้นตั้งตัวเองเมื่อฉันตระหนักว่าสิ่งที่ฉันคิดว่าเป็นพฤติกรรมที่ถูกต้อง

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

export const SOME_ACTION = 'SOME_ACTION';
export function someAction(items) {
  return (dispatch) => {
    dispatch(anotherAction(items));
  }
}

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


0

ฉันอยากจะแนะนำอีกทางเลือกหนึ่งที่ฉันพบว่าสะอาดที่สุด แต่มันต้องการreact-reduxหรืออะไรที่คล้าย ๆ - ฉันกำลังใช้คุณสมบัติแฟนซีอื่น ๆ อยู่สองสามทาง:

// actions.js
export const someAction = (items) => ({
    type: 'SOME_ACTION',
    payload: {items},
});
// Component.jsx
import {connect} from "react-redux";

const Component = ({boundSomeAction}) => (<div
    onClick={boundSomeAction}
/>);

const mapState = ({otherReducer: {items}}) => ({
    items,
});

const mapDispatch = (dispatch) => bindActionCreators({
    someAction,
}, dispatch);

const mergeProps = (mappedState, mappedDispatches) => {
    // you can only use what gets returned here, so you dont have access to `items` and 
    // `someAction` anymore
    return {
        boundSomeAction: () => mappedDispatches.someAction(mappedState.items),
    }
});

export const ConnectedComponent = connect(mapState, mapDispatch, mergeProps)(Component);
// (with  other mapped state or dispatches) Component.jsx
import {connect} from "react-redux";

const Component = ({boundSomeAction, otherAction, otherMappedState}) => (<div
    onClick={boundSomeAction}
    onSomeOtherEvent={otherAction}
>
    {JSON.stringify(otherMappedState)}
</div>);

const mapState = ({otherReducer: {items}, otherMappedState}) => ({
    items,
    otherMappedState,
});

const mapDispatch = (dispatch) => bindActionCreators({
    someAction,
    otherAction,
}, dispatch);

const mergeProps = (mappedState, mappedDispatches) => {
    const {items, ...remainingMappedState} = mappedState;
    const {someAction, ...remainingMappedDispatch} = mappedDispatch;
    // you can only use what gets returned here, so you dont have access to `items` and 
    // `someAction` anymore
    return {
        boundSomeAction: () => someAction(items),
        ...remainingMappedState,
        ...remainingMappedDispatch,
    }
});

export const ConnectedComponent = connect(mapState, mapDispatch, mergeProps)(Component);

หากคุณต้องการที่จะนำมาใช้ใหม่นี้คุณจะต้องดึงเฉพาะmapState, mapDispatchและmergePropsเข้าสู่ฟังก์ชั่นเพื่อนำมาใช้ในที่อื่น ๆ แต่นี้จะทำให้การอ้างอิงชัดเจนสมบูรณ์

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