1. what is state
state influences what you see on the screen.
2. the complexity of state management
react is great at reacting to the state changes and updating the UI . But managing that state can get very difficult as our application grows.
3. understanding the redux flow
image.png4. Setting up reducer and store
npm install --save redux
const redux = require('redux');
const createStore = redux.createStore;
const initialState={
counter: 0;
};
// Reducer
const rootReducer = (state = initialState, action) => {
if(action.type === 'ADD_COUNTER'){
return {
...state,
counter: state.counter + action.value
};
}
return state;
};
// Store
//a store needs to be initialized with a reducer.
// the reducer is strongly connected to the store.
// it's the only thing that may update the state in the end.
// that's why we need to pass the reducer to this
//creation function here because it's so cloesely // connected to the state.
const store = createStore(rootReducer);
console.log(store.getState());
// Dispatching Action
store.dispatch({type:'INC_COUNTER'});
store.dispatch({type:'ADD_COUNTER',value: 10);
console.log(store.getState());
// Subscription
// subscription make sure that I don't have to manually call getState function if I want to
// get the current state snapshot but to inform me whenever I need to get a new state because something changed.
// subscribe takes a arg, a function which will be executed when ever the state is updated.
store.subscribe(() => {
console.log('SUBSCRIBE', store.getState());
});
5. Connecting react to redux
npm install --save react-redux
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import reducer from './store/reducer';
import { Provider } from 'react-redux';
import './index.css';
import App from './App';
const store = createStore(reducer);
// Provider is a helper component which allows us to kind of inject our store into the react components.
ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root');
创建一个store文件夹,存放reducer.js文件
// reducer.js
const initialState = {
counter: 0
}
const reducer = (state = initialState, action) => {
return state;
}
export default reducer;
在组件里如何拿到 store?
// 组件 counter.js
...
// connect is not really a higher order component. it's a function which returns a higher order component.
import { connect } from 'react-redux';
...
class Counter extends Component {
...
}
const matStateToProps = state =>{
return {
ctr:state.counter
};
}
const mapDispatchToProps = dispatch =>{
return {
onIncrecementCounter: () => dispatch({type:'INCREMENT'})
}
}
export default connect(matStateToProps, mapDispatchToProps)(Counter);
...
reducers重构
// reducer.js
const initialState = {
counter: 0
}
const reducer = (state = initialState, action) => {
switch( action.type ){
case 'INCREMENT':
return {
counter: state.counter + 1
}
case 'DECREMENT':
return {
counter: state.counter -1
}
}
return state;
};
export default reducer;
6. Updating state Immutably
7. Combining multiple reducers
...
import { createStore, combineReducers } from 'redux';
import counterReducer from './store/reducers/counter';
import resultReducer from './store/reducers/result';
const rootReducer = combineReducers({
ctr: counterReducer,
res: resultReducer
});
const store = createStore(rootReducer);
...
引用store的地方需要修改(增加了命名空间)
const matStateToProps = state =>{
return {
// 修改前代码:ctr:state.counter,增加了ctr命名空间
ctr:state.ctr.counter
};
}
Middleware
image.pngimport React from 'react';
import ReactDOM from 'react-dom';
import { createStore, combineReducers, applyMiddleware } from 'redux';
import counterReducer from './store/reducers/counter';
import resultReducer from './store/reducers/result';
import { Provider } from 'react-redux';
import './index.css';
import App from './App';
// custom middleware
const logger = store => {
return next => {
return action => {
console.log('Middleware dispatching', action);
const result = next(action);
console.log('Middleware next state', store.getState());
return result;
}
}
};
const rootReducer = combineReducers({
ctr: counterReducer,
res: resultReducer
});
// applyMiddleware allows us to add our own middleware to the store.
const store = createStore(rootReducer, applyMiddleware(logger));
ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root');
the reducer function has to run synchronously.
there is no way you can execute asynchronous code in reducer.
some other way :
- execute asynchronous code with the help of so-called action creators.
// actions.js
export const INCREMENT='INCREMENT';
export const DECREMENT='DECREMENT';
...
const increment =()=>{
return {
type:INCREMENT
}
};
// 在Counter组件中使用 action creator
import { increment } from '../../store/actions/actions';
...
// before
const mapDispatchToProps = dispatch => {
return {
onIncrementCounter: () => dispatch({type: actionTypes.INCREMENT});
}
}
// after
const mapDispatchToProps = dispatch => {
return {
onIncrementCounter: () => dispatch(increment());
}
}
Handling Asynchronous code
take advantage of action creators to handle asynchronous code. to do this, we need redux-thunk.
redux-thunk adds a middleware to the project which allows your action creators to be precise to not reurn the action itself but return a function which will eventually dispatch an action. With this little trick, not returning the action itself but a function which will then dispatch one, we can run asynchronous code because the eventually dispatched one part is the part which may run asynchronously.
npm install --save redux-thunk
// index.js
import thunk from 'redux-thunk';
...
// applyMiddleware allows us to add our own middleware to the store.
const store =
createStore(rootReducer, applyMiddleware(logger,thunk));
//actions.js
export const INCREMENT='INCREMENT';
export const DECREMENT='DECREMENT';
...
// before
export const increment =()=>{
return {
type:INCREMENT
}
};
// after
export const saveIncrement = () => {
return {
type:INCREMENT
}
}
export const increment =()=>{
return function (dispatch,getState){
// we get dispatch here due to redux-thunk. middleware runs between
//the dispatching of an action and the point of time the ation reaches the
//reducer. the thing we do here is we still dispatch an action
// but then redux-thunk comes in , steps in, has access to the action there.
//basiclly blocks the old action and dispatches it again in the future.
// now the new action will reach the reducer but in-between,
//redux-thunk is able to wait because it can dispatch an action
//whenever it wants. This is the asyncronous part and that is
//exactly allowing us to execute some asynchronous code inside.
return dispatch => {
setTimeout(() =>{
const oldCounter = getState().counter;
dispatch(saveIncrement());
},2000);
}
}
};
image.png
why redux saga?
使用redux-thunk的一个结果是,会看到在 action creators 里, 处理异步的代码和dispatch action 的代码混合在一起。有时候我们会希望 action creators 或者说整个dispatch action 的过程 to be very clean. 我们不想在 action creators 里看到很多和dispatching action 不相关的代码。 这就是使用 redux-saga 的原因。
redux saga is a package which follows a different approach of working with asynchronous code and it doesn't mix it with the act of dispatching actions.
npm install --save redux-saga
sagas are essentially kind of functions which you run up on certain actions and which handle all your side effect logic and a side effect simply is something like accessing local storage, reaching out to a server .
新建文件夹 saga, 新建一个auth.js
// put in the end will just dispatch a new action.
import { put } from 'redux-saga/effects';
import * as actionTypes from '../actions/actionTypes';
export function* logoutSaga (action) {
yield localStorage.removeItem('token');
put({
type: actionTypes.AUTH_LOGOUT.
});
}
hook it up to the store.
// index.js
...
import createSagaMiddleware from 'redux-saga';
import { logoutSaga } from './store/sagas/auth';
const sagaMiddleware = createSagaMiddleware();
const store = createStore(rootReducer,
composeEnhancers(applyMiddleware(thunk, sagaMiddleware)
));
sagaMiddleware.run(logoutSaga);
moving logic from the action creator to Saga.
//auth.js
...
export const logout = () =>{
/* handle these side effects with redux saga. */
//localStorage.removeItem('token');
//localStorage.removeItem('expirationDate');
//localStorage.removeItem('userId');
return {
type:actionTypes.AUTH_LOGOUT
}
}
...
// actionType.js
...
export const AUTH_INITIATE_LOGOUT = 'AUTH_INITIATE_LOGOUT';
...
//auth.js
...
export const logout = () =>{
/* handle these side effects with redux saga. */
//localStorage.removeItem('token');
//localStorage.removeItem('expirationDate');
//localStorage.removeItem('userId');
// whenever I dispatch logout, I will now
// simply initiate the logout instead.
// now the goal is ,to listen to this newly created
// action creator and execute our logout
// saga generator whenever we detect the initiate
logout call.
// for that,I'll create a new file in sagas folder.
return {
type:actionTypes.AUTH_INITIATE_LOGOUT
}
}
...
在sagas文件夹中,新建index.js
// index.js
// takeEvery will allow us to listen to certain actions
// and do something when they occur.
import { takeEvery } from 'redux-saga/effects';
import * as actionTypes from '../actions/actionTypes';
import { logoutSaga } from './auth';
export function* watchAuth(){
yield takeEvery(actionTypes.AUTH_INITIATE_LOGOUT,lououtSaga);
}
// 全局 index.js 文件
// before
import { logoutSaga } from './store/sagas/auth';
// after
import { watchAuth } from './store/sagas';
// before
sagaMiddleware.run(logoutSaga);
// after
sagaMiddleware.run(watchAuth);
网友评论