添加 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
}
}