React / Redux - дія диспетчеризації при завантаженні програми / запуску


85

У мене є аутентифікація маркера з сервера, тому, коли моя програма Redux завантажується спочатку, мені потрібно зробити запит на цей сервер, щоб перевірити, чи аутентифікований користувач чи ні, і якщо так, я повинен отримати маркер.

Я виявив, що використовувати дії INIT ядра Redux не рекомендується, то як я можу відправити дію до того, як додаток буде відтворено?

Відповіді:


77

Ви можете надіслати дію в componentDidMountметоді Root, а в renderметоді - перевірити статус автентифікації.

Щось на зразок цього:

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
Для мене все componentWillMount()зробив. Я визначив просту функцію, що викликає всі дії, пов'язані з відправленням, в mapDispatchToProps()App.js і закликав її componentWillMount().
Froxx,

Це чудово, але використання mapDispatchToProps здається більш описовим. У чому полягає ваша аргументація використання замість mapStateToProps?
tcelferact

@ adc17 Oooops :) дякую за коментар. Я змінив свою відповідь!
Сергій Баранюк

@SerhiiBaraniuk не турбуйтеся. Ще одна річ: якщо припустити getAuth, що це творець дій, я думаю, ви хочете визначити його dispatchяк параметр mapDispatchToProps, тобто, const mapDispatchToProps = dispatch => {а потім зробити щось на зразок:getAuth: () => { dispatch(getAuth()); }
tcelferact

2
Я отримав цю помилку при спробі впровадити це рішенняUncaught 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

Я не був задоволений жодними рішеннями, які були висунуті для цього, і тоді мені спало на думку, що я думав про заняття, які потрібно проводити. Як щодо того, якби я просто створив клас для запуску, а потім вставив речі в componentDidMountметод і просто мав renderдисплей завантажувати екран?

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

А потім мати щось подібне:

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

Потім напишіть кілька дій відновлення, щоб асинхронізувати програму. Працює ласощі.


Тепер це рішення, що я шукав! Я вважаю, що ваше розуміння тут абсолютно правильне. Дякую.
YanivGK

26

Всі відповіді тут, здається, є варіаціями щодо створення кореневого компонента та запуску його в componentDidMount. Одне з речей, яке мені найбільше подобається при відновленні, це те, що він відокремлює отримання даних від життєвих циклів компонентів. Я не бачу причин, чому в цьому випадку має бути інакше.

Якщо ви імпортуєте свій магазин до кореневого index.jsфайлу, ви можете просто відправити свого творця дій (назвемо його initScript()) у цей файл, і він запуститься, перш ніж щось завантажиться.

Наприклад:

//index.js

store.dispatch(initScript());

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

1
Я новачок у реакції, але, заснований на прочитанні початкової документації щодо концепцій реакції та відновлення, я вважаю, що це найбільш підходящий спосіб. чи є якась перевага для створення цих ініціалізацій щодо componentDidMountподії?
kuzditomi

1
Це справді залежить від ситуації. Тож componentDidMountволя спрацьовує до монтажу певного компонента. Звільнення store.dispatch()до того ReactDOM.render () `пожежі до того , як додаток монтує. Це як componentWillMountдля всього додатка. Як новачкові, я вважаю, що краще дотримуватися використання методів життєвого циклу компонентів, оскільки він зберігає логіку, щільно пов'язану з тим, де він використовується. У міру того, як додатки стають дедалі складнішими, це стає важче продовжувати робити. Моя порада полягає в тому, щоб тримати це просто якомога довше.
Джош Піттман,

1
Нещодавно мені довелося скористатися наведеним вище підходом. У мене була кнопка входу в Google, і мені потрібно було запустити сценарій, щоб він працював, перш ніж програма завантажиться. Якби я зачекав, поки додаток завантажиться, а потім зателефонував, це просто зайняло б більше часу, щоб отримати відповідь, і затримка функціональності програми. Якщо виконання дій у життєвому циклі підходить для вашого випадку використання, дотримуйтесь життєвих циклів. Про них думати простіше. Хороший спосіб судити про це - уявити собі, як ви дивитесь на код через 6 місяців. Який підхід вам було б простіше зрозуміти інтуїтивно. Виберіть такий підхід.
Джош Піттман,

Привіт @JoshPittman, вам все одно потрібно підключити кореневий компонент, наприклад, "App" або щось подібне, щоб підписатися на оновлення стану redux. Отже, так само, як ви уникаєте відправлення дій лише за допомогою методу componentDidMount ().
Tuananhcwrs

1
Я кажу ТАК на вашу думку щодо відправлення. Redux не каже, що ми повинні відправляти дії зсередини компонента реакції. Redux, безумовно, незалежний від реакції.
Tuananhcwrs

16

Якщо ви використовуєте React Hooks, одне рядкове рішення буде:

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

Порожній масив забезпечив би його виклик лише один раз, при першому візуалізації.

Повний приклад:

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

Оновлення 2020 : Поряд з іншими рішеннями, я використовую проміжне програмне забезпечення Redux для перевірки кожного запиту на помилки спроб входу:

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

Оновлення 2018 : Ця відповідь стосується React Router 3

Я вирішив цю проблему, використовуючи реакційний маршрутизатор onEnter . Ось як виглядає код:

// 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
Щоб бути зрозумілим, маршрутизатор 4 не підтримує onEnter.
Роб Л

IntlProvider повинен дати вам підказку щодо кращого рішення .. Дивіться мою відповідь нижче.
Кріс Кемп

це використання старого
реакційного

3

За допомогою проміжного програмного забезпечення redux-saga ви можете це зробити чудово.

Просто визначте сагу, яка не стежить за відправленою дією (наприклад, за допомогою takeабо takeLatest) перед тим, як її ініціювати. Коли forkвиходить із кореневої саги, він запускається рівно один раз під час запуску програми.

Далі поданий неповний приклад, який вимагає знань про redux-sagaпакет, але ілюструє суть:

sagas / launchSaga.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];

Наведений вище код розподіляє дію launchStartта launchCompleteредукцію, яку слід створити. Це хороша практика створювати такі дії, оскільки вони дуже корисні, щоб повідомити державу про інші дії, коли запускання розпочнеться чи завершиться.

Потім ваша коренева сага повинна розгалужити цю launchSagaсагу:

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;

Будь ласка, прочитайте справді хорошу документацію про redux-saga, щоб отримати додаткову інформацію про неї.


сторінка не завантажуватиметься, доки ця дія не буде виконана правильно?
Марков

1

Ось відповідь, використовуючи останню версію React (16.8), Хуки:

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

Я використовував redux-thunk для отримання облікових записів у користувача з кінцевої точки API на програмі init, і це було асинхронізацією, тому дані надходили після надання мого додатка, і більшість наведених вище рішень не творили для мене чудес, а деякі з них знецінено. Тож я подивився на componentDidUpdate (). Отже, в основному на APP init я мав мати списки облікових записів з API, і мої облікові записи магазину redux мали б значення NULL або []. Вдалися до цього після.

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

Якщо ви використовуєте React Hooks, ви можете просто надіслати дію за допомогою React.useEffect

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

Я використовую цей шаблон для onAuthStateChangedпрослуховування регістрів

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

Використання: Apollo Client 2.0, React-Router v4, React 16 (Fiber)

Для вибраної відповіді використовуйте старий React Router v3. Мені потрібно було зробити "відправлення", щоб завантажити загальні налаштування програми. Хитрість полягає у використанні componentWillUpdate, хоча приклад використовує клієнт apollo, а не отримання рішень еквівалентно. Вам не потрібно букле

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
Цей код неповний і потребує обрізки для невідповідних частин питання.
Naoise Golden
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.