React / Redux - điều hành hành động khi tải ứng dụng / init


85

Tôi có xác thực mã thông báo từ máy chủ, vì vậy khi ứng dụng Redux của tôi được tải ban đầu, tôi cần thực hiện yêu cầu tới máy chủ này để kiểm tra xem người dùng có được xác thực hay không và nếu có, tôi sẽ nhận mã thông báo.

Tôi nhận thấy rằng không nên sử dụng các hành động INIT cốt lõi của Redux, vậy làm cách nào để gửi một hành động trước khi ứng dụng được hiển thị?

Câu trả lời:


77

Bạn có thể gửi một hành động trong componentDidMountphương thức Root và trong renderphương thức này, bạn có thể xác minh trạng thái xác thực.

Một cái gì đó như thế này:

class App extends Component {
  componentDidMount() {
    this.props.getAuth()
  }

  render() {
    return this.props.isReady
      ? <div> ready </div>
      : <div>not ready</div>
  }
}

const mapStateToProps = (state) => ({
  isReady: state.isReady,
})

const mapDispatchToProps = {
  getAuth,
}

export default connect(mapStateToProps, mapDispatchToProps)(App)

1
Đối với tôi componentWillMount()đã làm điều đó. Tôi đã xác định một hàm đơn giản gọi tất cả các hành động liên quan đến điều phối trong mapDispatchToProps()App.js và gọi nó vào componentWillMount().
Froxx

Điều này thật tuyệt, nhưng sử dụng mapDispatchToProps có vẻ mang tính mô tả nhiều hơn. Cơ sở lý luận của bạn đằng sau việc sử dụng mapStateToProps thay thế là gì?
tcelferact

@ adc17 Rất tiếc :) cảm ơn đã nhận xét. Tôi đã thay đổi câu trả lời của mình!
Serhii Baraniuk

@SerhiiBaraniuk đừng lo lắng. Một điều khác: giả sử getAuthlà một người sáng tạo hành động, tôi nghĩ rằng bạn muốn xác định dispatchnhư một param của mapDispatchToProps, ví dụ const mapDispatchToProps = dispatch => {và sau đó làm như sau:getAuth: () => { dispatch(getAuth()); }
tcelferact

2
Tôi gặp lỗi này khi cố gắng triển khai giải pháp nàyUncaught Error: Could not find "store" in either the context or props of "Connect(App)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(App)".
markhops

35

Tôi không hài lòng với bất kỳ giải pháp nào được đưa ra cho việc này, và sau đó tôi chợt nghĩ về việc các lớp cần được hiển thị. Còn nếu tôi vừa tạo một lớp để khởi động và sau đó đẩy mọi thứ vào componentDidMountphương thức và chỉ renderhiển thị màn hình tải?

<Provider store={store}>
  <Startup>
    <Router>
      <Switch>
        <Route exact path='/' component={Homepage} />
      </Switch>
    </Router>
  </Startup>
</Provider>

Và sau đó có một cái gì đó như thế này:

class Startup extends Component {
  static propTypes = {
    connection: PropTypes.object
  }
  componentDidMount() {
    this.props.actions.initialiseConnection();
  }
  render() {
    return this.props.connection
      ? this.props.children
      : (<p>Loading...</p>);
  }
}

function mapStateToProps(state) {
  return {
    connection: state.connection
  };
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(Actions, dispatch)
  };
}

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

Sau đó, viết một số hành động redux để khởi chạy ứng dụng của bạn không đồng bộ. Hoạt động một điều trị.


Bây giờ đó là giải pháp những gì tôi đang tìm kiếm! Tôi tin rằng cái nhìn sâu sắc của bạn ở đây là hoàn toàn đúng. Cảm ơn.
YanivGK

26

Tất cả các câu trả lời ở đây dường như là các biến thể trong việc tạo một thành phần gốc và kích hoạt nó trong componentDidMount. Một trong những điều tôi thích nhất về redux là nó tách dữ liệu tìm nạp từ các vòng đời của thành phần. Tôi không hiểu lý do gì khiến nó phải khác trong trường hợp này.

Nếu bạn đang nhập cửa hàng của mình vào index.jstệp gốc , bạn chỉ có thể cử người tạo hành động của mình (chúng ta hãy gọi nó initScript()) trong tệp đó và nó sẽ kích hoạt trước khi mọi thứ được tải.

Ví dụ:

//index.js

store.dispatch(initScript());

ReactDOM.render(
  <Provider store={store}>
    <Routes />
  </Provider>,
  document.getElementById('root')
);

1
Tôi là một người mới chơi react, nhưng dựa trên việc đọc các tài liệu ban đầu về các khái niệm react và redux, tôi tin rằng đây là cách phù hợp nhất. có lợi thế nào để tạo những khởi tạo này trên một componentDidMountsự kiện không?
kuzditomi

1
Nó thực sự phụ thuộc vào tình hình. Vì vậy, ý componentDidMountchí sẽ kích hoạt trước khi một thành phần cụ thể được gắn kết. Bắn store.dispatch()trước ReactDOM.render () `cháy trước khi gắn kết ứng dụng. Nó giống như một componentWillMountcho toàn bộ ứng dụng. Là một người mới, tôi nghĩ tốt hơn là nên sử dụng các phương thức vòng đời thành phần vì nó giữ logic kết hợp chặt chẽ với nơi nó đang được sử dụng. Khi các ứng dụng ngày càng trở nên phức tạp, điều này trở nên khó tiếp tục hơn. Lời khuyên của tôi là hãy giữ cho nó đơn giản chừng nào bạn có thể.
Josh Pittman

1
Tôi đã phải sử dụng cách tiếp cận ở trên gần đây. Tôi đã có nút đăng nhập google và tôi cần kích hoạt tập lệnh để làm cho nó hoạt động trước khi ứng dụng được tải lên. Nếu tôi đợi ứng dụng tải rồi thực hiện cuộc gọi thì sẽ mất nhiều thời gian hơn để nhận phản hồi và chức năng trong ứng dụng bị trì hoãn. Nếu thực hiện những việc trong một vòng đời phù hợp với trường hợp sử dụng của bạn, thì hãy tuân theo các vòng đời. Chúng đơn giản hơn để nghĩ về. Một cách tốt để đánh giá điều này là hình dung chính bạn đang xem mã 6 tháng kể từ bây giờ. Cách tiếp cận nào sẽ dễ hiểu hơn đối với bạn. Chọn cách tiếp cận đó.
Josh Pittman

Xin chào @JoshPittman, bạn vẫn cần kết nối thành phần gốc, ví dụ: "Ứng dụng" hoặc thứ gì đó tương tự để đăng ký bản cập nhật trạng thái redux. Vì vậy, nó giống như bạn chỉ tránh gửi hành động từ phương thức componentDidMount () của nó.
Tuananhcwrs

1
Tôi nói CÓ với quan điểm của bạn về việc điều động. Redux không nói rằng chúng ta phải gửi các hành động từ bên trong một thành phần phản ứng. Redux chắc chắn độc lập với phản ứng.
Tuananhcwrs

15

Nếu bạn đang sử dụng React Hooks, một giải pháp dòng đơn sẽ là:

useEffect(() => store.dispatch(handleAppInit()), []);

Mảng trống sẽ đảm bảo nó chỉ được gọi một lần, trong lần hiển thị đầu tiên.

Ví dụ đầy đủ:

import React, { useEffect } from 'react';
import { Provider } from 'react-redux';

import AppInitActions from './store/actions/appInit';

function App() {
  useEffect(() => store.dispatch(AppInitActions.handleAppInit()), []);
  return (
    <Provider store={store}>
      <div>
        Hello World
      </div>
    </Provider>
  );
}

export default App;

11

Cập nhật 2020 : Cùng với các giải pháp khác, tôi đang sử dụng phần mềm trung gian Redux để kiểm tra từng yêu cầu về các lần đăng nhập không thành công:

export default () => next => action => {
  const result = next(action);
  const { type, payload } = result;

  if (type.endsWith('Failure')) {
    if (payload.status === 401) {
      removeToken();

      window.location.replace('/login');
    }
  }

  return result;
};

Cập nhật 2018 : Câu trả lời này dành cho React Router 3

Tôi đã giải quyết vấn đề này bằng cách sử dụng đạo cụ react -router onEnter . Đây là cách mã trông giống như:

// this function is called only once, before application initially starts to render react-route and any of its related DOM elements
// it can be used to add init config settings to the application
function onAppInit(dispatch) {
  return (nextState, replace, callback) => {
    dispatch(performTokenRequest())
      .then(() => {
        // callback is like a "next" function, app initialization is stopped until it is called.
        callback();
      });
  };
}

const App = () => (
  <Provider store={store}>
    <IntlProvider locale={language} messages={messages}>
      <div>
        <Router history={history}>
          <Route path="/" component={MainLayout} onEnter={onAppInit(store.dispatch)}>
            <IndexRoute component={HomePage} />
            <Route path="about" component={AboutPage} />
          </Route>
        </Router>
      </div>
    </IntlProvider>
  </Provider>
);

11
Chỉ cần nói rõ là react-router 4 không hỗ trợ onEnter.
Rob L

IntlProvider sẽ cung cấp cho bạn một gợi ý về giải pháp tốt hơn .. Hãy xem câu trả lời của tôi bên dưới.
Chris Kemp

cái này sử dụng
react

3

Với phần mềm trung gian redux-saga, bạn có thể làm điều đó một cách độc đáo.

Chỉ cần xác định một saga không theo dõi hành động đã gửi (ví dụ: với takehoặc takeLatest) trước khi được kích hoạt. Khi chỉnh sửa forktừ câu chuyện gốc như vậy, nó sẽ chạy chính xác một lần khi khởi động ứng dụng.

Sau đây là một ví dụ không đầy đủ yêu cầu một chút kiến ​​thức về redux-sagagói nhưng minh họa điểm:

sagas / khởi chạySaga.js

import { call, put } from 'redux-saga/effects';

import { launchStart, launchComplete } from '../actions/launch';
import { authenticationSuccess } from '../actions/authentication';
import { getAuthData } from '../utils/authentication';
// ... imports of other actions/functions etc..

/**
 * Place for initial configurations to run once when the app starts.
 */
const launchSaga = function* launchSaga() {
  yield put(launchStart());

  // Your authentication handling can go here.
  const authData = yield call(getAuthData, { params: ... });
  // ... some more authentication logic
  yield put(authenticationSuccess(authData));  // dispatch an action to notify the redux store of your authentication result

  yield put(launchComplete());
};

export default [launchSaga];

Đoạn mã trên gửi một hành động launchStartlaunchCompleteredux mà bạn nên tạo. Một phương pháp hay là tạo các hành động như vậy vì chúng có ích để thông báo trạng thái làm những việc khác bất cứ khi nào khởi chạy bắt đầu hoặc hoàn thành.

Câu chuyện gốc của bạn sau đó sẽ rẽ nhánh launchSagacâu chuyện này :

sagas / index.js

import { fork, all } from 'redux-saga/effects';
import launchSaga from './launchSaga';
// ... other saga imports

// Single entry point to start all sagas at once
const root = function* rootSaga() {
  yield all([
    fork( ... )
    // ... other sagas
    fork(launchSaga)
  ]);
};

export default root;

Vui lòng đọc tài liệu thực sự tốt về redux-saga để biết thêm thông tin về nó.


trang sẽ không tải cho đến khi hành động này hoàn thành đúng không?
Markov

1

Đây là câu trả lời bằng cách sử dụng phiên bản mới nhất trong React (16.8), Hooks:

import { appPreInit } from '../store/actions';
// app preInit is an action: const appPreInit = () => ({ type: APP_PRE_INIT })
import { useDispatch } from 'react-redux';
export default App() {
    const dispatch = useDispatch();
    // only change the dispatch effect when dispatch has changed, which should be never
    useEffect(() => dispatch(appPreInit()), [ dispatch ]);
    return (<div>---your app here---</div>);
}

0

Tôi đang sử dụng redux-thunk để tìm nạp Tài khoản dưới quyền người dùng từ điểm cuối API trên app init và nó không đồng bộ nên dữ liệu sẽ đến sau khi ứng dụng của tôi được hiển thị và hầu hết các giải pháp ở trên không làm tôi ngạc nhiên và một số mất giá. Vì vậy, tôi đã tìm đến componentDidUpdate (). Vì vậy, về cơ bản trên APP init, tôi phải có danh sách tài khoản từ API và tài khoản cửa hàng redux của tôi sẽ là null hoặc []. Đã nghỉ đến cái này sau khi.

class SwitchAccount extends Component {

    constructor(props) {
        super(props);

        this.Format_Account_List = this.Format_Account_List.bind(this); //function to format list for html form drop down

        //Local state
        this.state = {
                formattedUserAccounts : [],  //Accounts list with html formatting for drop down
                selectedUserAccount: [] //selected account by user

        }

    }



    //Check if accounts has been updated by redux thunk and update state
    componentDidUpdate(prevProps) {

        if (prevProps.accounts !== this.props.accounts) {
            this.Format_Account_List(this.props.accounts);
        }
     }


     //take the JSON data and work with it :-)   
     Format_Account_List(json_data){

        let a_users_list = []; //create user array
        for(let i = 0; i < json_data.length; i++) {

            let data = JSON.parse(json_data[i]);
            let s_username = <option key={i} value={data.s_username}>{data.s_username}</option>;
            a_users_list.push(s_username); //object
        }

        this.setState({formattedUserAccounts: a_users_list}); //state for drop down list (html formatted)

    }

     changeAccount() {

         //do some account change checks here
      }

      render() {


        return (
             <Form >
                <Form.Group >
                    <Form.Control onChange={e => this.setState( {selectedUserAccount : e.target.value})} as="select">
                        {this.state.formattedUserAccounts}
                    </Form.Control>
                </Form.Group>
                <Button variant="info" size="lg" onClick={this.changeAccount} block>Select</Button>
            </Form>
          );


         }    
 }

 const mapStateToProps = state => ({
      accounts: state.accountSelection.accounts, //accounts from redux store
 });


  export default connect(mapStateToProps)(SwitchAccount);

0

Nếu bạn đang sử dụng React Hooks, bạn có thể chỉ cần gửi một hành động bằng cách sử dụng React.useEffect

React.useEffect(props.dispatchOnAuthListener, []);

Tôi sử dụng mẫu này cho trình onAuthStateChangednghe đăng ký

function App(props) {
  const [user, setUser] = React.useState(props.authUser);
  React.useEffect(() => setUser(props.authUser), [props.authUser]);
  React.useEffect(props.dispatchOnAuthListener, []);
  return <>{user.loading ? "Loading.." :"Hello! User"}<>;
}

const mapStateToProps = (state) => {
  return {
    authUser: state.authentication,
  };
};

const mapDispatchToProps = (dispatch) => {
  return {
    dispatchOnAuthListener: () => dispatch(registerOnAuthListener()),
  };
};

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

-1

Sử dụng: Apollo Client 2.0, React-Router v4, React 16 (Fiber)

Câu trả lời được chọn là sử dụng React Router v3 cũ. Tôi cần thực hiện 'điều phối' để tải cài đặt chung cho ứng dụng. Thủ thuật là sử dụng componentWillUpdate, mặc dù ví dụ đang sử dụng ứng dụng khách apollo và không tìm nạp các giải pháp là tương đương. Bạn không cần giới hạn

SettingsLoad.js

import React, { Component } from 'react';
import { connect } from 'react-redux';
import {bindActionCreators} from "redux";
import {
  graphql,
  compose,
} from 'react-apollo';

import {appSettingsLoad} from './actions/appActions';
import defQls from './defQls';
import {resolvePathObj} from "./utils/helper";
class SettingsLoad extends Component {

  constructor(props) {
    super(props);
  }

  componentWillMount() { // this give infinite loop or no sense if componente will mount or not, because render is called a lot of times

  }

  //componentWillReceiveProps(newProps) { // this give infinite loop
  componentWillUpdate(newProps) {

    const newrecord = resolvePathObj(newProps, 'getOrgSettings.getOrgSettings.record');
    const oldrecord = resolvePathObj(this.props, 'getOrgSettings.getOrgSettings.record');
    if (newrecord === oldrecord) {
      // when oldrecord (undefined) !== newrecord (string), means ql is loaded, and this will happens
      //  one time, rest of time:
      //     oldrecord (undefined) == newrecord (undefined)  // nothing loaded
      //     oldrecord (string) == newrecord (string)   // ql loaded and present in props
      return false;
    }
    if (typeof newrecord ==='undefined') {
      return false;
    }
    // here will executed one time
    setTimeout(() => {
      this.props.appSettingsLoad( JSON.parse(this.props.getOrgSettings.getOrgSettings.record));
    }, 1000);

  }
  componentDidMount() {
    //console.log('did mount this props', this.props);

  }

  render() {
    const record = resolvePathObj(this.props, 'getOrgSettings.getOrgSettings.record');
    return record
      ? this.props.children
      : (<p>...</p>);
  }
}

const withGraphql = compose(

  graphql(defQls.loadTable, {
    name: 'loadTable',
    options: props => {
      const optionsValues = {  };
      optionsValues.fetchPolicy = 'network-only';
      return optionsValues ;
    },
  }),
)(SettingsLoad);


const mapStateToProps = (state, ownProps) => {
  return {
    myState: state,
  };
};

const mapDispatchToProps = (dispatch) => {
  return bindActionCreators ({appSettingsLoad, dispatch }, dispatch );  // to set this.props.dispatch
};

const ComponentFull = connect(
  mapStateToProps ,
  mapDispatchToProps,
)(withGraphql);

export default ComponentFull;

App.js

class App extends Component<Props> {
  render() {

    return (
        <ApolloProvider client={client}>
          <Provider store={store} >
            <SettingsLoad>
              <BrowserRouter>
            <Switch>
              <LayoutContainer
                t={t}
                i18n={i18n}
                path="/myaccount"
                component={MyAccount}
                title="form.myAccount"
              />
              <LayoutContainer
                t={t}
                i18n={i18n}
                path="/dashboard"
                component={Dashboard}
                title="menu.dashboard"
              />

2
Mã này chưa hoàn chỉnh và cần được cắt bớt những phần không liên quan đến câu hỏi.
Naoise Golden
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.