redux 記錄一下 redux 的一些用法,如果想學習 redux,建議看 "官方文檔" ,另外推薦一本 "huzidaha" 寫的 "react小書" ,裡面講解了一些 react 和 redux 的原理。 start 運行如下命令,不瞭解 npx 的,可以看一下 "阮一峰的文章" 。 然後安裝 ...
redux
記錄一下 redux 的一些用法,如果想學習 redux,建議看官方文檔,另外推薦一本huzidaha寫的react小書,裡面講解了一些 react 和 redux 的原理。
start
運行如下命令,不瞭解 npx 的,可以看一下阮一峰的文章。
// 腳手架創建項目
npx create-react-app redux-test
// 進入文件夾
cd redux-test
// 啟動 react
npm run start
然後安裝 redux:
npm i -S redux react-redux
接著把 src 下的文件都刪掉幾個,只剩兩個文件:
src/
|--index.js
|--serviceWorker.js
redux and react-redux
打開上面留下的 index.js,刪掉裡面的代碼,敲下自己的代碼,然後刷新網頁。
// 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();
mapDispatchToProps
connect
中的mapDispatchToProps
可以是一個函數,也可以是一個對象,函數寫法如上。
當mapDispatchToProps
是一個對象時,redux 會把它裡面的屬性作為actionCreator
交給dispatch
使用,簡單來說,就是 redux 幫你把對象裡面的屬性封裝為函數型的mapDispatchToProps
,寫法如下:
// 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
connect
可以使用裝飾器的寫法。
// 裝飾器的 babel 插件
npm i -S @babel/plugin-proposal-decorators
然後進行配置 plugin,這裡有兩種配置方法:
使用 create-react-app 的配置
// 暴露配置 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
可以使用中間件 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>
);
}
}
combineReducers
當你有多個 reducer 時,可以使用combineReducers
,進行合併。
import { createStore, combineReducers } from 'redux'
const allReducer = combineReducers({
reducerOne,
reducersTwo
})
store = createStore(allReducer)
bindActionCreators
把 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 的感受,看,不如動手去敲。