서버에서 토큰 인증을 받았으므로 Redux 앱이 처음로드 될 때 사용자가 인증되었는지 여부를 확인하기 위해이 서버에 요청을해야하며, 그렇다면 토큰을 받아야합니다.
Redux 코어 INIT 작업을 사용하는 것이 권장되지 않는다는 것을 알았는데, 앱이 렌더링되기 전에 어떻게 작업을 전달할 수 있습니까?
답변:
루트 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)
getAuth
액션 크리에이터 라고 가정하면 , 즉 dispatch
의 매개 변수 로 정의 하고 다음과 같은 작업을 수행해야한다고 생각합니다.mapDispatchToProps
const mapDispatchToProps = dispatch => {
getAuth: () => { dispatch(getAuth()); }
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)".
나는 이것을 위해 제시된 해결책에 만족하지 않았고, 렌더링이 필요한 클래스에 대해 생각하고 있다는 생각이 들었습니다. 방금 시작을위한 클래스를 만든 다음 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 작업을 작성하여 앱을 비 동기화하십시오. 치료 효과가 있습니다.
여기의 모든 대답은 루트 구성 요소를 만들고 componentDidMount에서 실행하는 변형 인 것 같습니다. 내가 redux에 대해 가장 좋아하는 것 중 하나는 구성 요소 수명주기에서 데이터 가져 오기를 분리한다는 것입니다. 이 경우에 다른 이유가 없습니다.
스토어를 루트 index.js
파일 로 가져 오는 경우 해당 파일에서 액션 생성자 (라고 부르 자 initScript()
)를 디스패치하면 무엇이든로드되기 전에 실행됩니다.
예를 들면 :
//index.js
store.dispatch(initScript());
ReactDOM.render(
<Provider store={store}>
<Routes />
</Provider>,
document.getElementById('root')
);
componentDidMount
이벤트 에서 이러한 초기화를 만드는 이점이 있습니까?
componentDidMount
특정 구성 요소가 탑재되기 전에 실행됩니다. 발사 store.dispatch()
ReactDOM.render 전에 ()`응용 프로그램 마운트하기 전에 발생합니다. componentWillMount
전체 앱을위한 것과 같습니다 . 초보자로서 로직이 사용되는 위치와 밀접하게 결합되어 있기 때문에 컴포넌트 라이프 사이클 메소드를 사용하는 것이 더 낫다고 생각합니다. 앱이 점점 복잡 해짐에 따라이 작업을 계속하기가 더 어려워집니다. 내 조언은 가능한 한 간단하게 유지하는 것입니다.
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;
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>
);
으로 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];
위의 코드 는 생성해야하는 launchStart
및 launchComplete
redux 액션을 전달합니다 . 실행이 시작되거나 완료 될 때마다 다른 작업을 수행하도록 상태에 알리는 데 유용한 작업을 만드는 것이 좋습니다.
루트 사가는이 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 의 정말 좋은 문서를 읽으 십시오.
다음은 최신 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>);
}
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);
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);
사용 : 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"
/>
componentWillMount()
그 일을했습니다.mapDispatchToProps()
App.js의 모든 디스패치 관련 작업을 호출하는 간단한 함수를 정의하고componentWillMount()
.