記錄一下 redux 的一些用法,若是想學習 redux,建議看官方文檔,另外推薦一本huzidaha寫的react小書,裏面講解了一些 react 和 redux 的原理。javascript
運行以下命令,不瞭解 npx 的,能夠看一下阮一峯的文章。html
// 腳手架建立項目 npx create-react-app redux-test // 進入文件夾 cd redux-test // 啓動 react npm run start
而後安裝 redux:java
npm i -S redux react-redux
接着把 src 下的文件都刪掉幾個,只剩兩個文件:react
src/ |--index.js |--serviceWorker.js
打開上面留下的 index.js,刪掉裏面的代碼,敲下本身的代碼,而後刷新網頁。git
// index.js import React, { Component } from 'react' import ReactDOM from 'react-dom' import * as serviceWorker from './serviceWorker' import { createStore } from 'redux' import { connect, Provider } from 'react-redux' // actionTypes const NUM_ADD = 'NUM_ADD'; // actionCreator function addNum () { return { type: NUM_ADD } } // reducer const initialState = { num: 0 } function counter (state = initialState, action) { switch (action.type) { case NUM_ADD: return { num: state.num + 1 } default: return state } } // store const store = createStore(counter) // App class App extends Component { render() { return ( <div> {this.props.num} <button onClick={this.props.onClickAdd}>add</button> </div> ); } } // connect(mapStateToProps, mapDispatchToProps)(component) const mapStateToProps = state => { return { num: state.num } } const mapDispatchToProps = dispatch => { return { onClickAdd: () => { dispatch(addNum()) } } } App = connect(mapStateToProps, mapDispatchToProps)(App) // Provider 傳入 store ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root')); serviceWorker.unregister();
connect
中的mapDispatchToProps
能夠是一個函數,也能夠是一個對象,函數寫法如上。github
當mapDispatchToProps
是一個對象時,redux 會把它裏面的屬性做爲actionCreator
交給dispatch
使用,簡單來講,就是 redux 幫你把對象裏面的屬性封裝爲函數型的mapDispatchToProps
,寫法以下:shell
// index.js ... // App class App extends Component { render() { return ( <div> {this.props.num} // 這裏也進行了修改 // onClickAdd -> addNum <button onClick={this.props.addNum}>add</button> </div> ); } } ... // connect App = connect( state => state, { addNum } )(App) ...
connect
可使用裝飾器的寫法。npm
// 裝飾器的 babel 插件 npm i -S @babel/plugin-proposal-decorators
而後進行配置 plugin,這裏有兩種配置方法:json
使用 create-react-app 的配置redux
// 暴露配置 npm run eject // 會進行確認 Are you sure you want to eject? This action is permanent.(y/n) y
而後會看到文件夾內多了一些東西,打開項目根路徑下的 package.json 文件,找到 babel 配置項:
// package.json ... "babel": { "presets": [ "react-app" ], "plugins": [["@babel/plugin-proposal-decorators", { "legacy": true }]] }, ...
使用獨立文件配置 babel:
打開項目根路徑下的 package.json 文件,找到 babel 配置項,將他刪掉:
// package.json ... // 把這個刪掉 "babel": { "presets": [ "react-app" ], }, ...
而後在項目根目錄建立.babelrc
文件。
// .babelrc { "presets": ["react-app"], "plugins": [ ["@babel/plugin-proposal-decorators", { "legacy": true }] }
配置完 babel 以後,打開 src 下的 index.js 進行修改:
// index.js ... // App // 裝飾器寫法 @connect( state => state, { addNum } ) class App extends Component { render() { return ( <div> {this.props.num} <button onClick={this.props.addNum}>add</button> </div> ); } } ...
注意:對於裝飾器的支持只是實驗性的,將來可能會改動。
可使用中間件 redux-thunk 進行異步操做,它可讓actionCreator
不返回action
對象,而是返回一個函數,能夠在函數內封裝邏輯。
function incrementIfOdd() { // 接收兩個參數 // getState() 能夠拿到 store 中的 state return (dispatch, getState) => { const { counter } = getState(); if (counter % 2 === 0) { return; } dispatch(increment()); }; }
首先仍是安裝:
npm i -S redux-thunk
而後打開 index.js 進行修改:
// index.js ... import {createStore, applyMiddleware} from 'redux' import thunk from 'redux-thunk' ... // store const store = createStore(counter, applyMiddleware(thunk)) ...
異步操做有不少,這裏將 num 的增長推遲爲 1s 後才進行:
// index.js // actionCreator function addNum () { return { type: NUM_ADD } } function addNumAsync () { return dispatch => { setTimeout(() => { dispatch({type: NUM_ADD}) }, 1000) } } // App @connect( state => state, { addNum, addNumAsync } ) class App extends Component { render() { return ( <div> {this.props.num} <button onClick={this.props.addNum}>add</button> <button onClick={this.props.addNumAsync}>add after 1s</button> </div> ); } }
當你有多個 reducer 時,可使用combineReducers
,進行合併。
import { createStore, combineReducers } from 'redux' const allReducer = combineReducers({ reducerOne, reducersTwo }) store = createStore(allReducer)
把 actionCreator 傳到一個子組件中,卻不想讓這個組件覺察到 Redux 的存在,並且不但願把 dispatch 或 store 傳給它時,可使用bindActionCreators
。
這裏使用官方文檔的例子:
// TodoActionCreators.js export function addTodo(text) { return { type: 'ADD_TODO', text }; } export function removeTodo(id) { return { type: 'REMOVE_TODO', id }; }
// SomeComponent.js import { Component } from 'react'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import * as TodoActionCreators from './TodoActionCreators'; console.log(TodoActionCreators); // { // addTodo: Function, // removeTodo: Function // } class TodoListContainer extends Component { constructor(props) { super(props); const {dispatch} = props; // 這是一個很好的 bindActionCreators 的使用示例: // 你想讓你的子組件徹底不感知 Redux 的存在。 // 咱們在這裏對 action creator 綁定 dispatch 方法, // 以便稍後將其傳給子組件。 this.boundActionCreators = bindActionCreators(TodoActionCreators, dispatch); console.log(this.boundActionCreators); // { // addTodo: Function, // removeTodo: Function // } } componentDidMount() { // 由 react-redux 注入的 dispatch: let { dispatch } = this.props; // 注意:這樣是行不通的: // TodoActionCreators.addTodo('Use Redux') // 你只是調用了建立 action 的方法。 // 你必需要同時 dispatch action。 // 這樣作是可行的: let action = TodoActionCreators.addTodo('Use Redux'); dispatch(action); } render() { // 由 react-redux 注入的 todos: let { todos } = this.props; return <TodoList todos={todos} {...this.boundActionCreators} />; // 另外一替代 bindActionCreators 的作法是 // 直接把 dispatch 函數看成 prop 傳遞給子組件,但這時你的子組件須要 // 引入 action creator 而且感知它們 // return <TodoList todos={todos} dispatch={dispatch} />; } } export default connect(state => ({ todos: state.todos }))(TodoListContainer)
我的學習 redux 的感覺,看,不如動手去敲。