React / Redux-앱로드 / 초기화에 대한 작업 전달


85

서버에서 토큰 인증을 받았으므로 Redux 앱이 처음로드 될 때 사용자가 인증되었는지 여부를 확인하기 위해이 서버에 요청을해야하며, 그렇다면 토큰을 받아야합니다.

Redux 코어 INIT 작업을 사용하는 것이 권장되지 않는다는 것을 알았는데, 앱이 렌더링되기 전에 어떻게 작업을 전달할 수 있습니까?

답변:


77

루트 componentDidMount방식으로 작업을 발송할 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 :) 의견 주셔서 감사합니다. 내 대답을 변경했습니다!
Serhii Baraniuk

@SerhiiBaraniuk 걱정 마세요. 다른 한 가지 : getAuth액션 크리에이터 라고 가정하면 , 즉 dispatch의 매개 변수 로 정의 하고 다음과 같은 작업을 수행해야한다고 생각합니다.mapDispatchToPropsconst 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);

그런 다음 몇 가지 redux 작업을 작성하여 앱을 비 동기화하십시오. 치료 효과가 있습니다.


이제 그것이 제가 찾던 해결책입니다! 여기에 대한 귀하의 통찰력이 완벽하다고 믿습니다. 감사.
YanivGK

26

여기의 모든 대답은 루트 구성 요소를 만들고 componentDidMount에서 실행하는 변형 인 것 같습니다. 내가 redux에 대해 가장 좋아하는 것 중 하나는 구성 요소 수명주기에서 데이터 가져 오기를 분리한다는 것입니다. 이 경우에 다른 이유가 없습니다.

스토어를 루트 index.js파일 로 가져 오는 경우 해당 파일에서 액션 생성자 (라고 부르 자 initScript())를 디스패치하면 무엇이든로드되기 전에 실행됩니다.

예를 들면 :

//index.js

store.dispatch(initScript());

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

1
저는 반응 초보이지만 반응 및 redux 개념에 대한 초기 문서를 읽은 결과 이것이 가장 적합한 방법이라고 생각합니다. componentDidMount이벤트 에서 이러한 초기화를 만드는 이점이 있습니까?
kuzditomi

1
상황에 따라 다릅니다. 따라서는 componentDidMount특정 구성 요소가 탑재되기 전에 실행됩니다. 발사 store.dispatch()ReactDOM.render 전에 ()`응용 프로그램 마운트하기 전에 발생합니다. componentWillMount전체 앱을위한 것과 같습니다 . 초보자로서 로직이 사용되는 위치와 밀접하게 결합되어 있기 때문에 컴포넌트 라이프 사이클 메소드를 사용하는 것이 더 낫다고 생각합니다. 앱이 점점 복잡 해짐에 따라이 작업을 계속하기가 더 어려워집니다. 내 조언은 가능한 한 간단하게 유지하는 것입니다.
조쉬 피트 맨

1
최근에 위의 접근 방식을 사용해야했습니다. Google 로그인 버튼이 있었고 앱이로드되기 전에 작동하도록 스크립트를 실행해야했습니다. 앱이로드 될 때까지 기다린 다음 호출하면 응답을받는 데 더 오래 걸리고 앱의 기능이 지연됩니다. 수명주기에서 수행하는 작업이 사용 사례에 적합하다면 수명주기를 고수하십시오. 생각하기가 더 간단합니다. 이것을 판단하는 좋은 방법은 6 개월 후 코드를보고있는 자신을 상상하는 것입니다. 직관적으로 이해하기 쉬운 접근 방식. 그 접근 방식을 선택하십시오.
Josh Pittman

안녕하세요 @JoshPittman, 여러분은 여전히 ​​루트 컴포넌트, 예를 들어 "App"또는 redux 상태의 업데이트를 구독하기 위해 비슷한 것을 연결해야합니다. 따라서 componentDidMount () 메서드에서만 액션을 디스패치하지 않는 것과 같습니다.
Tuananhcwrs

1
나는 파견에 대해 당신의 요점에 예라고 말합니다. Redux는 반응 컴포넌트 내부에서 액션을 디스패치해야한다고 말하지 않습니다. Redux는 분명히 반응과 독립적입니다.
Tuananhcwrs 19

15

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입니다.

나는 react-router onEnter props를 사용하여이 문제를 해결했습니다 . 코드는 다음과 같습니다.

// 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
명확하게 말하면 react-router 4는 onEnter를 지원하지 않습니다.
Rob L

IntlProvider는 더 나은 솔루션에 대한 힌트를 제공해야합니다. 아래 내 대답을 참조하십시오.
Chris Kemp

오래된이 사용하는 반응 라우터 v3의 모습을 내 대답에
stackdave

3

으로 REDUX-사가 당신이 멋지게 그것을 할 수있는 미들웨어.

트리거되기 전에 디스패치 된 작업 (예 : take또는 takeLatest)을 감시하지 않는 사가를 정의하십시오 . 그런 식으로 fork루트 사가에서 ed하면 앱 시작시 정확히 한 번 실행됩니다.

다음은 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];

위의 코드 는 생성해야하는 launchStartlaunchCompleteredux 액션을 전달합니다 . 실행이 시작되거나 완료 될 때마다 다른 작업을 수행하도록 상태에 알리는 데 유용한 작업을 만드는 것이 좋습니다.

루트 사가는이 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 의 정말 좋은 문서를 읽으 십시오.


이 작업이 올바르게 완료 될 때까지 페이지가로드되지 않습니까?
Markov

1

다음은 최신 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

redux-thunk를 사용하여 앱 초기화의 API 끝점에서 사용자의 계정을 가져 왔고 비동기식이어서 앱이 렌더링 된 후 데이터가 들어 왔고 위의 대부분의 솔루션은 저에게 놀라운 일이 아니었고 일부는 다음과 같습니다. 감가 상각. 그래서 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를 사용하는 것입니다. 예제에서는 아폴로 클라이언트를 사용하고 있으며 솔루션을 가져 오지 않는 것은 동일합니다. 당신은 boucle의 필요하지 않습니다

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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.