Truy cập trạng thái Redux trong một trình tạo hành động?


296

Nói rằng tôi có những điều sau đây:

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

Và trong trình tạo hành động đó, tôi muốn truy cập trạng thái cửa hàng toàn cầu (tất cả các bộ giảm tốc). Có tốt hơn để làm điều này:

import store from '../store';

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

hoặc này:

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

    dispatch(anotherAction(items));
  }
}

Câu trả lời:


522

Có nhiều ý kiến ​​khác nhau về việc truy cập trạng thái trong người tạo hành động có phải là một ý tưởng hay không:

  • Người tạo Redux Dan Abramov cảm thấy rằng nó nên được giới hạn: "Một số ít trường hợp sử dụng mà tôi nghĩ có thể chấp nhận được là để kiểm tra dữ liệu được lưu trong bộ nhớ cache trước khi bạn đưa ra yêu cầu hoặc để kiểm tra xem bạn có được xác thực hay không (nói cách khác là thực hiện công văn có điều kiện). tôi nghĩ rằng thông qua dữ liệu chẳng hạn như state.something.itemstrong một tác giả hành động chắc chắn là một mô hình chống và không được khuyến khích vì nó che khuất lịch sử thay đổi: nếu có một lỗi và itemskhông đúng, rất khó để theo dõi nơi những giá trị không chính xác xuất phát từ, vì họ là đã là một phần của hành động, thay vì được tính toán trực tiếp bởi một bộ giảm để đáp ứng với một hành động. Vì vậy, hãy cẩn thận thực hiện điều này. "
  • Người duy trì Redux hiện tại Mark Erikson nói rằng nó ổn và thậm chí được khuyến khích sử dụng getStatetrong thunks - đó là lý do tại sao nó tồn tại . Ông thảo luận về những ưu và nhược điểm của việc truy cập trạng thái trong các nhà sáng tạo hành động trong bài đăng trên blog của mình Idiomatic Redux: Suy nghĩ về Thunks, Sagas, Trừu tượng và Tái sử dụng .

Nếu bạn thấy rằng bạn cần điều này, cả hai cách tiếp cận bạn đề xuất đều ổn. Cách tiếp cận đầu tiên không yêu cầu bất kỳ phần mềm trung gian nào:

import store from '../store';

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

Tuy nhiên, bạn có thể thấy rằng nó phụ thuộc vào storeviệc là một singleton được xuất từ ​​một số mô-đun. Chúng tôi không khuyên bạn vì điều đó khiến việc thêm kết xuất máy chủ vào ứng dụng của bạn khó khăn hơn nhiều vì trong hầu hết các trường hợp trên máy chủ, bạn sẽ muốn có một cửa hàng riêng cho mỗi yêu cầu . Vì vậy, về mặt kỹ thuật phương pháp này hoạt động, chúng tôi không khuyên bạn nên xuất một cửa hàng từ một mô-đun.

Đây là lý do tại sao chúng tôi đề xuất cách tiếp cận thứ hai:

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

    dispatch(anotherAction(items));
  }
}

Nó sẽ yêu cầu bạn sử dụng Redux Thunk middleware nhưng nó hoạt động tốt cả trên máy khách và trên máy chủ. Bạn có thể đọc thêm về Redux Thunk và tại sao nó cần thiết trong trường hợp này ở đây .

Lý tưởng nhất là hành động của bạn không nên là Fat fat và nên chứa càng ít thông tin càng tốt, nhưng bạn nên thoải mái làm những gì tốt nhất cho bạn trong ứng dụng của riêng bạn. Câu hỏi thường gặp về Redux có thông tin về việc phân tách logic giữa người tạo hành động và trình giảm tốcthời gian có thể hữu ích khi sử dụng getStatetrong trình tạo hành động .


5
Tôi có một tình huống khi chọn thứ gì đó trong thành phần có thể kích hoạt PUT hoặc POST, tùy thuộc vào việc cửa hàng có chứa hay không dữ liệu liên quan đến thành phần đó. Có tốt hơn không khi đặt logic nghiệp vụ cho lựa chọn PUT / POST trong thành phần thay vì trình tạo hành động dựa trên thunk?
Abbeyusbass

3
Thực hành tốt nhất là gì? Bây giờ tôi đang đối mặt với vấn đề tương tự khi tôi đang sử dụng getState trong trình tạo hành động của mình. Trong trường hợp của tôi, tôi sử dụng nó để xác định xem các giá trị nếu một biểu mẫu có các thay đổi đang chờ xử lý (và nếu vậy tôi sẽ gửi một hành động hiển thị hộp thoại).
Arne H. Bitubekk

42
Thật tốt khi đọc từ cửa hàng trong trình tạo hành động. Tôi khuyến khích bạn sử dụng bộ chọn để bạn không phụ thuộc vào hình dạng trạng thái chính xác.
Dan Abramov

2
Tôi đang sử dụng một phần mềm trung gian để gửi dữ liệu đến mixpanel. Vì vậy, tôi có một khóa meta bên trong hành động. Tôi cần chuyển các biến khác nhau từ trạng thái sang mixpanel. Đặt chúng trên các trình tạo hành động dường như là một mô hình chống. Điều gì sẽ là cách tiếp cận tốt nhất để xử lý các loại trường hợp sử dụng?
Aakash Sigdel

2
Trời ơi! Tôi đã không thắt nút mà redux thunk nhận getStatelàm tham số thứ hai, tôi đã bị nứt đầu, cảm ơn bạn rất nhiều
Lai32290

33

Khi kịch bản của bạn đơn giản, bạn có thể sử dụng

import store from '../store';

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

Nhưng đôi khi bạn action creatorcần kích hoạt nhiều hành động

ví dụ như yêu cầu async để bạn cần REQUEST_LOAD REQUEST_LOAD_SUCCESS REQUEST_LOAD_FAILhành động

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
                });
            }
        })
    }
}

Lưu ý: bạn cần redux-thunk để trả về hàm trong trình tạo hành động


3
Tôi có thể hỏi liệu có nên kiểm tra trạng thái của trạng thái 'đang tải không để yêu cầu ajax khác không được thực hiện trong khi yêu cầu đầu tiên kết thúc không?
JoeTidee

@JoeTidee Trong ví dụ, việc gửi trạng thái tải được thực hiện. Nếu bạn thực hiện hành động này bằng nút chẳng hạn, bạn sẽ kiểm tra nếu loading === truecó và vô hiệu hóa nút.
croraf

4

Tôi đồng ý với @Bloomca. Truyền giá trị cần thiết từ cửa hàng vào chức năng điều phối như một đối số có vẻ đơn giản hơn so với xuất cửa hàng. Tôi đã làm một ví dụ ở đây:

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);

Phương pháp này khá logic.
Ahmet Şimşek

Đây là cách chính xác để làm điều đó và làm thế nào tôi làm điều đó. Người tạo hành động của bạn không cần biết toàn bộ trạng thái, chỉ có bit liên quan đến nó.
Tro

3

Tôi muốn chỉ ra rằng việc đọc từ cửa hàng không tệ lắm - có thể sẽ thuận tiện hơn nhiều khi quyết định những gì nên làm dựa trên cửa hàng, hơn là chuyển mọi thứ cho thành phần và sau đó là một tham số của một chức năng. Tôi hoàn toàn đồng ý với Dan, tốt hơn hết là không sử dụng cửa hàng dưới dạng singletone, trừ khi bạn chắc chắn 100% rằng bạn sẽ chỉ sử dụng để kết xuất phía máy khách (nếu không thì khó có thể theo dõi lỗi).

Gần đây tôi đã tạo ra một thư viện để đối phó với tính dài dòng của redux và tôi nghĩ rằng nên đưa mọi thứ vào phần mềm trung gian, vì vậy bạn có thể sử dụng như một mũi tiêm phụ thuộc.

Vì vậy, ví dụ của bạn sẽ trông như thế:

import { createSyncTile } from 'redux-tiles';

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

Tuy nhiên, như bạn có thể thấy, chúng tôi thực sự không sửa đổi dữ liệu ở đây, vì vậy rất có thể chúng tôi chỉ có thể sử dụng bộ chọn này ở nơi khác để kết hợp nó ở một nơi khác.


1

Trình bày một cách khác để giải quyết điều này. Điều này có thể tốt hơn hoặc tồi tệ hơn giải pháp của Dan, tùy thuộc vào ứng dụng của bạn.

Bạn có thể lấy trạng thái từ các bộ giảm tốc thành các hành động bằng cách chia hành động thành 2 chức năng riêng biệt: thứ nhất yêu cầu dữ liệu, hành động thứ hai trên dữ liệu. Bạn có thể làm điều đó bằng cách sử dụng redux-loop.

Đầu tiên 'vui lòng yêu cầu dữ liệu'

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

Trong bộ giảm tốc, chặn yêu cầu và cung cấp dữ liệu cho hành động ở giai đoạn thứ hai bằng cách sử dụng 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))
        }
    }
}

Với dữ liệu trong tay, hãy làm bất cứ điều gì bạn muốn ban đầu

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

Hy vọng điều này sẽ giúp được ai đó.


0

Tôi biết tôi đến bữa tiệc muộn ở đây, nhưng tôi đến đây để lấy ý kiến ​​về mong muốn sử dụng nhà nước của mình trong hành động, và sau đó thành lập chính mình, khi tôi nhận ra những gì tôi nghĩ là hành vi đúng.

Đây là nơi một bộ chọn có ý nghĩa nhất đối với tôi. Thành phần của bạn đưa ra yêu cầu này nên được nói với thời gian để đưa ra yêu cầu thông qua lựa chọn.

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

Nó có thể cảm thấy như bị rò rỉ trừu tượng, nhưng thành phần của bạn rõ ràng cần phải gửi một tin nhắn và tải trọng tin nhắn nên chứa trạng thái thích hợp. Thật không may, câu hỏi của bạn không có ví dụ cụ thể vì chúng tôi có thể làm việc thông qua 'mô hình tốt hơn' của các công cụ chọn và hành động theo cách đó.


0

Tôi muốn đề xuất một phương án khác mà tôi thấy sạch nhất, nhưng nó đòi hỏi react-reduxhoặc một cái gì đó tương tự - tôi cũng đang sử dụng một vài tính năng ưa thích khác trên đường đi:

// 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);

Nếu bạn muốn sử dụng này, bạn sẽ phải trích xuất các cụ mapState, mapDispatchmergePropsvào chức năng để tái sử dụng ở những nơi khác, nhưng làm cho này phụ thuộc nào hoàn toàn rõ ràng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.