添加 redux
yarn add next-redux-saga next-redux-wrapper react-redux redux redux-devtools-extension redux-saga es6-promise redux-thunk
新建./redux/actions.js
import { actionTypes } from "./actionTypes"; export function failure(error) { return { type: actionTypes.FAILURE, error }; } export function increment() { return { type: actionTypes.INCREMENT }; } export function decrement() { return { type: actionTypes.DECREMENT }; } export function reset() { return { type: actionTypes.RESET }; } export function loadData() { return { type: actionTypes.LOAD_DATA }; } export function loadDataSuccess(data) { return { type: actionTypes.LOAD_DATA_SUCCESS, data }; } export function startClock() { return { type: actionTypes.START_CLOCK }; } export function tickClock(isServer) { return { type: actionTypes.TICK_CLOCK, light: !isServer, ts: Date.now() }; }
新建./redux/actionTypes.js
export const actionTypes = { FAILURE: "FAILURE", INCREMENT: "INCREMENT", DECREMENT: "DECREMENT", RESET: "RESET", LOAD_DATA: "LOAD_DATA", LOAD_DATA_SUCCESS: "LOAD_DATA_SUCCESS", START_CLOCK: "START_CLOCK", TICK_CLOCK: "TICK_CLOCK" };
新建./redux/reducer.js
import { actionTypes } from "./actionTypes"; export const exampleInitialState = { count: 0, error: false, lastUpdate: 0, light: false, placeholderData: null }; function reducer(state = exampleInitialState, action) { switch (action.type) { case actionTypes.FAILURE: return { ...state, ...{ error: action.error } }; case actionTypes.INCREMENT: return { ...state, ...{ count: state.count + 1 } }; case actionTypes.DECREMENT: return { ...state, ...{ count: state.count - 1 } }; case actionTypes.RESET: return { ...state, ...{ count: exampleInitialState.count } }; case actionTypes.LOAD_DATA_SUCCESS: return { ...state, ...{ placeholderData: action.data } }; case actionTypes.TICK_CLOCK: return { ...state, ...{ lastUpdate: action.ts, light: !!action.light } }; default: return state; } } export default reducer;
新建./redux/store.js
import { applyMiddleware, createStore } from "redux"; import createSagaMiddleware from "redux-saga"; import rootReducer, { exampleInitialState } from "./reducer"; import rootSaga from "./saga"; const bindMiddleware = middleware => { if (process.env.NODE_ENV !== "production") { const { composeWithDevTools } = require("redux-devtools-extension"); return composeWithDevTools(applyMiddleware(...middleware)); } return applyMiddleware(...middleware); }; function configureStore(initialState = exampleInitialState) { const sagaMiddleware = createSagaMiddleware(); const store = createStore(rootReducer, initialState, bindMiddleware([sagaMiddleware])); store.sagaTask = sagaMiddleware.run(rootSaga); return store; } export default configureStore;
使用 redux-sage 实现异步函数
新建./redux/saga.js
import { all, call, delay, put, take, takeLatest } from "redux-saga/effects"; import es6promise from "es6-promise"; import "isomorphic-unfetch"; import { actionTypes } from "./actionTypes"; import { failure, loadDataSuccess, tickClock } from "./actions"; es6promise.polyfill(); function* runClockSaga() { yield take(actionTypes.START_CLOCK); while (true) { yield put(tickClock(false)); yield delay(1000); } } function* loadDataSaga() { try { const res = yield fetch("https://jsonplaceholder.typicode.com/users"); const data = yield res.json(); yield put(loadDataSuccess(data)); } catch (err) { yield put(failure(err)); } } function* rootSaga() { yield all([call(runClockSaga), takeLatest(actionTypes.LOAD_DATA, loadDataSaga)]); } export default rootSaga;
修改 pages/_app.js 文件
import React from "react"; import App from "next/app"; import "../assets/css/styles.less"; import { Provider } from "react-redux"; import withRedux from "next-redux-wrapper"; import withReduxSaga from "next-redux-saga"; import createStore from "../redux/store"; class MyApp extends App { static async getInitialProps({ Component, ctx }) { let pageProps = {}; if (Component.getInitialProps) { pageProps = await Component.getInitialProps({ ctx }); } return { pageProps }; } render() { const { Component, pageProps, store } = this.props; return ( <Provider store={store}> <Component {...pageProps} /> </Provider> ); } } export default withRedux(createStore)(withReduxSaga(MyApp));
react-redux 中 mapDispatchToProps 的几种方式
首先需要先明白什么是 action,什么是 action 生成器,什么又是触发 action 函数
action 是一个对象
{ type: actionTypes.TICK_CLOCK, light: !isServer, ts: Date.now() }
action 生成器是一个函数
function tickClock(isServer) { return { type: actionTypes.TICK_CLOCK, light: !isServer, ts: Date.now() }; }
触发 action 函数
function dispatchTickClock(dispatch){ return dispatch(tickClock(false)) }
mapDispatchToProps
[mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function)
传递对象
- 如果传递的是一个对象,那么每个定义在该对象的函数都将被当作 Redux action creator,对象所定义的方法名将作为属性名;每个方法将返回一个新的函数,函数中 dispatch 方法会将 action creator 的返回值作为参数执行,Redux 自动发出
import React, { Component } from "react"; import { connect } from "react-redux"; import { increment, decrement, reset } from "../../redux/actions"; import { Button } from "antd"; class Counter extends Component { render() { const { count, increment, decrement, reset } = this.props; return ( <div> <style jsx>{` div { padding: 0 0 20px 0; } `}</style> <h1> Count: <span>{count}</span> </h1> <Button type="primary" onClick={increment}> 当前counter +1 </Button> <Button type="primary" onClick={decrement}> 当前counter -1 </Button> <Button type="primary" onClick={reset}> 当前counter Reset </Button> </div> ); } } function mapStateToProps(state) { return { count: state.count }; } const mapActionCreators = { increment, decrement, reset }; export default connect( mapStateToProps, mapActionCreators )(Counter);
bindActionCreators 辅助函数
- 如果传递的是一个函数,该函数将接收一个 dispatch 函数,然后由你来决定如何返回一个对象,这个对象通过 dispatch 函数与 action creator 以某种方式绑定在一起(提示:你也许会用到 Redux 的辅助函数 bindActionCreators()。
import React, { Component } from "react"; import { connect } from "react-redux"; import { increment, decrement, reset } from "../../redux/actions"; import { Button } from "antd"; import { bindActionCreators } from "redux"; class Counter extends Component { render() { const { count, increment, decrement, reset } = this.props; return ( <div> <style jsx>{` div { padding: 0 0 20px 0; } `}</style> <h1> Count: <span>{count}</span> </h1> <Button type="primary" onClick={increment}> 当前counter +1 </Button> <Button type="primary" onClick={decrement}> 当前counter -1 </Button> <Button type="primary" onClick={reset}> 当前counter Reset </Button> </div> ); } } function mapStateToProps(state) { return { count: state.count }; } function mapDispatchToProps(dispatch) { return { increment: bindActionCreators(increment, dispatch), decrement: bindActionCreators(decrement, dispatch), reset: bindActionCreators(reset, dispatch) }; } export default connect( mapStateToProps, mapDispatchToProps )(Counter);
上面的写法 mapDispatchToProps 可以将函数以不同的名称加入到 props 中,如果不需要变更名称,也可以简写
function mapDispatchToProps(dispatch) { return bindActionCreators({increment,decrement,reset},dispatch); }
其中 action 生成器
export function increment() { return { type: actionTypes.INCREMENT }; } export function decrement() { return { type: actionTypes.DECREMENT }; }
这里直接使用了 redux 的 bindActionCreators 辅助函数去绑定 action 触发函数
参数传入函数且不用 bindActionCreators 辅助函数
import React, { Component } from "react"; import { connect } from "react-redux"; import { increment, decrement, reset } from "../../redux/actions"; import { Button } from "antd"; class Counter extends Component { render() { const { count, increment, decrement, reset } = this.props; return ( <div> <style jsx>{` div { padding: 0 0 20px 0; } `}</style> <h1> Count: <span>{count}</span> </h1> <Button type="primary" onClick={increment}> 当前counter +1 </Button> <Button type="primary" onClick={decrement}> 当前counter -1 </Button> <Button type="primary" onClick={reset}> 当前counter Reset </Button> </div> ); } } function mapStateToProps(state) { return { count: state.count }; } function mapActionCreators(dispatch) { return { increment: () => { return dispatch(increment()); }, decrement: () => { return dispatch(decrement()); }, reset: () => { return dispatch(reset()); } }; } export default connect( mapStateToProps, mapActionCreators )(Counter);
dispatch 注入组件
- 如果你省略这个 mapDispatchToProps 参数,默认情况下,dispatch 会注入到你的组件 props 中。
import React, { Component } from "react"; import { connect } from "react-redux"; import { increment, decrement, reset } from "../../redux/actions"; import { Button } from "antd"; class Counter extends Component { increment = () => { this.props.dispatch(increment()); }; decrement = () => { this.props.dispatch(decrement()); }; reset = () => { this.props.dispatch(reset()); }; render() { const { count } = this.props; return ( <div> <style jsx>{` div { padding: 0 0 20px 0; } `}</style> <h1> Count: <span>{count}</span> </h1> <Button type="primary" onClick={this.increment}> 当前counter +1 </Button> <Button type="primary" onClick={this.decrement}> 当前counter -1 </Button> <Button type="primary" onClick={this.reset}> 当前counter Reset </Button> </div> ); } } function mapStateToProps(state) { return { count: state.count }; } export default connect(mapStateToProps)(Counter);
其中 action 生成器
export function increment() { return { type: actionTypes.INCREMENT }; } export function decrement() { return { type: actionTypes.DECREMENT }; }
这里的方法就是 increment()生成器返回一个 action,然后交由 action 触发器 dispatch 去触发
使用 redux-thunk
import React, { Component } from "react"; import { connect } from "react-redux"; import { increment, decrement, reset } from "../../redux/actions"; import { Button } from "antd"; class Counter extends Component { render() { const { count, increment, decrement, reset } = this.props; return ( <div> <style jsx>{` div { padding: 0 0 20px 0; } `}</style> <h1> Count: <span>{count}</span> </h1> <Button type="primary" onClick={increment}> 当前counter +1 </Button> <Button type="primary" onClick={decrement}> 当前counter -1 </Button> <Button type="primary" onClick={reset}> 当前counter Reset </Button> </div> ); } } function mapStateToProps(state) { return { count: state.count }; } const mapActionCreators = { increment, decrement, reset }; export default connect( mapStateToProps, mapActionCreators )(Counter);
其中 action 生成器需要修改为返回函数,由于返回的是一个函数,redux 可以在里面执行一些异步操作,action 也可以用来进行网络请求
export function increment() { return dispatch => { dispatch({ type: actionTypes.INCREMENT }); }; } export function decrement() { return dispatch => { dispatch({ type: actionTypes.DECREMENT }); }; } export function reset() { return dispatch => { dispatch({ type: actionTypes.RESET }); }; }
使用装饰器
yarn add @babel/plugin-proposal-decorators --dev yarn add babel-plugin-transform-decorators-legacy --dev
然后修改.babelrc
{ "presets": ["next/babel"], "plugins": [ [ "@babel/plugin-proposal-decorators", { "legacy": true } ], [ "import", { "libraryName": "antd", "style": "less" } ] ] }
将组件的代码更新
import React, { Component } from "react"; import { connect } from "react-redux"; import { increment, decrement, reset } from "../../redux/actions"; import { Button } from "antd"; @connect( state => ({ count: state.count }), dispatch => bindActionCreators({ increment, decrement, reset }, dispatch) ) class Counter extends Component { render() { const { count, increment, decrement, reset } = this.props; return ( <div> <style jsx>{` div { padding: 0 0 20px 0; } `}</style> <h1> Count: <span>{count}</span> </h1> <Button type="primary" onClick={increment}> 当前counter +1 </Button> <Button type="primary" onClick={decrement}> 当前counter -1 </Button> <Button type="primary" onClick={reset}> 当前counter Reset </Button> </div> ); } } export default Counter;
如果你之前没有使用装饰器的话,vscode 会报出一个警告,现在去除这个警告
tsconfig.json
{ "compilerOptions": { "experimentalDecorators": true, "allowJs": true, }, }
没有 tsconfig.json 就用 jsconfig.json
{ "compilerOptions": { "experimentalDecorators": true } }
[错误解决]https://github.com/zeit/next.js/issues/5231