添加 redux
寫過 react 稍微復雜一些應用的話,應該都對 redux(mobx)有一定的了解.這次將 redux 引入到項目中
因為之前寫項目的習慣,更喜歡使用 redux-thunk 改寫 dispatch 進行異步請求.
redux-thunk 改寫了 dispatch API,使其具備接受一個函數作為參數的能力.既然是函數,就可以執行,也就可以發起 ajax 請求了.
yarn add next-redux-wrapper react-redux redux redux-devtools-extension es6-promise redux-thunk redux-logger
創建 actions
首先,還是將 action 的常量抽離出去單獨做一個文件 actionTypes.js.當前項目是剛起步,所以暫時不需要將 reducer 和 action 分成小模塊.
新建./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()
};
}
創建 action 常量文件
新建./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"
};
創建 reducer
新建./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 thunkMiddleware from "redux-thunk";
import rootReducer, { exampleInitialState } from "./reducer";
const bindMiddleware = middleware => {
if (process.env.NODE_ENV !== "production") {
const { composeWithDevTools } = require("redux-devtools-extension");
// 開發模式打印redux信息
const { logger } = require("redux-logger");
middleware.push(logger);
return composeWithDevTools(applyMiddleware(...middleware));
}
return applyMiddleware(...middleware);
};
function configureStore(initialState = exampleInitialState) {
const store = createStore(rootReducer, initialState, bindMiddleware([thunkMiddleware]));
return store;
}
export default configureStore;
修改 pages/_app.js 文件
redux 會創建一個全局的 state 包裹在 app 的最高一級,然后通過注入的形式添加到下級的各級組件中.現在就在自定義的_app.js 文件中添加 Provider
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 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)(MyApp);
react-redux 中 mapDispatchToProps 的幾種方式
講一下關於 redux 的集中注入方式,同時將 redux 如何在下級組件使用的方式展示出來.
重點知道什么是 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
}
}