React / Redux - uygulama yükleme / başlatma sırasında eylemi gönder


88

Bir sunucudan belirteç kimlik doğrulamam var, bu nedenle Redux uygulamam ilk yüklendiğinde, kullanıcının kimlik doğrulamasının yapılıp yapılmadığını kontrol etmek için bu sunucuya bir istekte bulunmam gerekiyor ve evet ise belirteç almalıyım.

Redux çekirdek INIT eylemlerinin kullanılmasının tavsiye edilmediğini fark ettim, bu yüzden uygulama oluşturulmadan önce bir eylemi nasıl gönderebilirim?

Yanıtlar:


78

Kök componentDidMountyöntemde bir eylem gönderebilir ve renderyöntemde kimlik doğrulama durumunu doğrulayabilirsiniz.

Bunun gibi bir şey:

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
Benim için componentWillMount()bunu yaptım. mapDispatchToProps()App.js'deki tüm göndermeyle ilgili eylemleri çağıran basit bir işlev tanımladım ve çağırdım componentWillMount().
Froxx

Bu harika, ancak mapDispatchToProps kullanmak daha açıklayıcı görünüyor. Bunun yerine mapStateToProps kullanmanın arkasındaki mantığınız nedir?
tcelferact

@ adc17 Oooops :) yorum için teşekkürler. Cevabımı değiştirdim!
Serhii Baraniuk

1
dan @ adc17 alıntı doc :[mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function): If an object is passed, each function inside it is assumed to be a Redux action creator. An object with the same function names, but with every action creator wrapped into a dispatch call so they may be invoked directly, will be merged into the component’s props.
Serhii Baraniuk

2
Bu çözümü uygulamaya çalışırken bu hatayı aldımUncaught 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

Bunun için öne sürülen hiçbir çözümden memnun değildim ve sonra, dönüştürülmesi gereken sınıflar hakkında düşündüğüm aklıma geldi. Peki ya başlangıç ​​için bir sınıf oluştursam ve ardından bir şeyleri componentDidMountyönteme aktarırsam ve yalnızca renderbir yükleme ekranı görüntülesem?

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

Ve sonra şuna benzer bir şey yapın:

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

Ardından, uygulamanızı eşzamansız olarak başlatmak için bazı yeniden düzenleme eylemleri yazın. Bir tedavi işe yarar.


İşte aradığım çözüm bu! Buradaki anlayışınızın tamamen doğru olduğuna inanıyorum. Teşekkürler.
YanivGK

29

Buradaki tüm cevaplar, bir kök bileşen oluşturma ve onu componentDidMount'ta ateşleme konusunda farklılıklar gibi görünüyor. Redux ile ilgili en sevdiğim şeylerden biri, veri getirmeyi bileşen yaşam döngülerinden ayırmasıdır. Bu durumda farklı olması için hiçbir neden göremiyorum.

Mağazanızı kök index.jsdosyaya aktarıyorsanız, eylem oluşturucunuzu (hadi onu çağıralım initScript()) o dosyaya gönderebilirsiniz ve herhangi bir şey yüklenmeden önce tetiklenir.

Örneğin:

//index.js

store.dispatch(initScript());

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

1
Ben bir react acemisiyim, ancak react ve redux kavramlarıyla ilgili ilk belgeleri okumaya dayanarak, bunun en uygun yol olduğuna inanıyorum. Bu başlatmaları bir componentDidMountolayda oluşturmanın herhangi bir avantajı var mı?
kuzditomi

1
Gerçekten duruma bağlı. Böylece componentDidMountbelirli bir bileşen monte edilmeden önce ateşlenecektir. Ateşleme store.dispatch()ReactDOM.render önce () `uygulama bağlar önce yangınlar. componentWillMountTüm uygulama için bir tür gibi . Bir acemi olarak, bileşen yaşam döngüsü yöntemlerini kullanmaya devam etmenin daha iyi olacağını düşünüyorum çünkü mantığı kullanıldığı yere sıkı bir şekilde bağlı tutar. Uygulamalar gittikçe daha karmaşık hale geldikçe, bunu yapmaya devam etmek zorlaşıyor. Benim tavsiyem, yapabildiğin kadar basit tutmaktır.
Josh Pittman

1
Yakın zamanda yukarıdaki yaklaşımı kullanmak zorunda kaldım. Bir Google giriş düğmem vardı ve uygulama yüklenmeden önce çalışmasını sağlamak için bir komut dosyası çalıştırmam gerekiyordu. Uygulamanın yüklenmesini bekledikten sonra aramayı yapsaydım, yanıtı almam daha uzun sürer ve uygulamadaki işlevselliği geciktirirdi. Bir yaşam döngüsü içinde bir şeyler yapmak sizin kullanım durumunuz için işe yarıyorsa, yaşam döngülerine bağlı kalın. Düşünmeleri daha kolaydır. Bunu yargılamanın iyi bir yolu, bundan 6 ay sonra koda baktığınızı hayal etmektir. Hangi yaklaşımı sezgisel olarak anlamanız daha kolay olurdu. Bu yaklaşımı seçin.
Josh Pittman

1
Ayrıca, redux güncellemesine gerçekten abone olmanıza gerek yok, sadece göndermeniz gerekiyor. Bu yaklaşımın tüm amacı bu, redux'un bir şeyi yapan (veri getirme, bir eylemi başlatma vb.) Ve sonucu kullanan (oluşturma, yanıtlama, vb.) Ayrıştırmalarından yararlanıyorum.
Josh Pittman

2
Sevk konusunda size EVET diyorum. Redux, eylemleri bir tepki bileşeninin içinden göndermemiz gerektiğini söylemiyor. Redux kesinlikle tepkiden bağımsızdır.
Tuananhcwrs

18

React Hooks kullanıyorsanız, tek satırlık bir çözüm

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

Boş dizi, ilk işlemede yalnızca bir kez çağrılmasını sağlar.

Tam örnek:

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

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

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

11

2020 Güncellemesi : Diğer çözümlerin yanı sıra, başarısız oturum açma girişimleri için her isteği kontrol etmek için Redux ara yazılımını kullanıyorum:

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 Güncellemesi : Bu cevap React Router 3 içindir

React -router onEnter props kullanarak bu sorunu çözdüm . Kod şu şekilde görünür:

// 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
Açık olmak gerekirse, yönlendirici 4, onEnter'i desteklemiyor.
Rob L

IntlProvider size daha iyi bir çözüm için bir ipucu vermelidir .. Aşağıdaki cevabıma bakın.
Chris Kemp

bu eski react-router
v3'ü kullan, cevabıma

3

İle redux-destan güzelce yapabilir Middleware sistemi.

Sadece tetiklenmeden önce gönderilen eylemi (örneğin takeveya ile takeLatest) izlemeyen bir destan tanımlayın . Ne zaman forko uygulamanın başlangıçta tam olarak bir kez çalışacağı gibi kök destan dan ed.

Aşağıdaki, redux-sagapaket hakkında biraz bilgi gerektiren, ancak konuyu açıklayan eksik bir örnektir :

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

Yukarıdaki kod , oluşturmanız gereken bir launchStartve yeniden düzenleme launchCompleteeylemi gönderir . Lansman başladığında veya tamamlandığında başka şeyler yapması için durumu bildirmek için kullanışlı olduklarından bu tür eylemler oluşturmak iyi bir uygulamadır.

Kök destanınız daha sonra bu launchSagadestanı çatallaştırmalıdır:

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;

Lütfen daha fazla bilgi için redux-saga'nın gerçekten iyi belgelerini okuyun .


bu eylem doğru tamamlanana kadar sayfa yüklenmeyecek mi?
Markov

2

İşte en son React (16.8) Hooks'u kullanarak bir cevap:

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

Uygulama, Sağlayıcı altında olmalıdır. TypeScript'i mutlu etmek için, gönderim etrafında fazladan bir kapanış ekledim: useEffect (() => {dispatch (AppInit ())}, []).
PEZO

0

Uygulama başlangıcındaki bir API uç noktasından bir kullanıcı altındaki Hesapları almak için redux-thunk kullanıyordum ve eşzamansızdı, bu nedenle veriler, uygulamam oluşturulduktan sonra geliyordu ve yukarıdaki çözümlerin çoğu benim için harikalar yaratmadı ve bazıları amortismana tabi. Bu yüzden componentDidUpdate () 'e baktım. Temelde APP init'te API'den hesap listelerine sahip olmam gerekiyordu ve redux mağaza hesaplarım null veya [] olacaktı. Daha sonra buna başvurdu.

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 Hook'larını kullanıyorsanız, React.useEffect'i kullanarak basitçe bir eylem gönderebilirsiniz.

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

Bu kalıbı onAuthStateChangeddinleyici kaydı için kullanıyorum

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

Seçilen cevap eski React Router v3'ü kullanıyor. Uygulama için genel ayarları yüklemek için 'gönder' yapmam gerekiyordu. İşin püf noktası componentWillUpdate'i kullanmaktır, ancak örnek apollo istemcisi kullanıyor ve çözümleri getirmemek eşdeğerdir. Buketine ihtiyacın yok

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
Bu kod eksiktir ve soruyla ilgisiz kısımlar için kırpılması gerekir.
Naoise Golden
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.