Redux入門教程
三大原則
-
單一數據源
整個應用的state被儲存在一棵object tree中,並且這個object tree只存在於唯一一個store中。
每個應用只有一個store
-
State是隻讀的
唯一改變state 的方法就是觸發action,action是一個用於描述已發生事件的普通對象。
-
使用純函數來執行修改
爲了描述action如何改變state tree,需要編寫reducer
基礎概念
Action
Action是把數據從應用傳到store的有效載荷,它是store數據的唯一途徑。
Reducer
Reducers指定了應用狀態的變化如何響應actions併發到store。
Store
Store是把它們聯繫到一起的對象,它有以下職責:
- 維持應用的state;
- 提供
getState()
方法獲取state; - 提供
dispath(action)
方法更新state; - 通過
subscribe(listener)
註冊監聽器;subscribe(listener)
方法返回一個函數,調用這個函數就可以解除監聽。
工作流程
假設React組件上綁定了一個Action - Click事件:
- 當點擊事件時,dispatch會分發對應的action
- action通過reducers對狀態進行更新並返回到store當中
- store獲得新狀態之後又重新渲染到視圖上
- 如此反覆…
手把手實例
首先,我們通過create-react-app demo03
命令,創建一個React腳手架應用。
npm i redux --save
和 npm i react-redux --save
分別安裝Redux和React-Redux
PS:
我在剛開始接觸Redux時,一直都沒理清楚這兩者的關係,實際上這是兩個獨立的存在。這裏摘錄網上的資料進行解釋。
Redux:數據處理中心
React-Redux:連接組件和數據中心,也就是把React和Redux聯繫起來
npm i node-sass --save
安裝Sass,方便樣式編寫。
PS:
create-react-app 腳手架中已經添加了 sass-loader 的支持,所以只需要安裝 node-sass 插件即可;
但如何是配置less則沒有這麼方便了,因爲腳手架本身並沒有配置關於less文件的解析。添加Less相關步驟如下:
運行
npm run eject
命令,該命令會將腳手架中隱藏的配置都展示出來,此過程不可逆。之後,打開config目錄下的webpack.config.js文件,找到
// style files regexes
註釋位置,添加以下兩行代碼// 添加 less 解析規則 const lessRegex = /\.less$/; const lessModuleRegex = /\/module\.less$/;
找到rules屬性配置,添加less解析配置
// Less 解析配置 { test: lessRegex, exclude: lessModuleRegex, use: getStyleLoaders( { importLoaders: 2, sourceMap: isEnvProduction && shouldUseSourceMap, }, 'less-loader' ), sideEffects: true, }, { test: lessModuleRegex, use: getStyleLoaders( { importLoaders: 2, sourceMap: isEnvProduction && shouldUseSourceMap, modules: true, getLocalIdent: getCSSModuleLocalIdent, }, 'less-loader' ) }
配置完成後,安裝less和less-loader插件即可
npm i less less-loader --save
此時,即可使用Less了
接着,我們將目錄中多餘的文件刪除,src文件夾下僅留index.js文件即可。下面是我當前src文件夾下的目錄結構:
- src
- contaienrs // 用於使用react-redux,連接視圖層和store
- css // 存放各個視圖層用到的css文件,通過index.scss文件進入導入
- reducers // 用於存儲reducers文件
- actionCreator 用於存儲action操作,便於管理
- actionType 用於存儲action type的變量,便於管理
- views // 用於存儲視圖
index.js
index.scss // 用於導入所有scss文件
將目錄結構整理好之後,就可以開始實例了,例子較爲簡單,主要是想通過例子學習和鞏固React-redux的使用。
首先,創建store。在Redux當中,store是用來存儲狀態的,同時它也是唯一的,整個項目當中有且只有一個store。
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.scss';
import ToDoList from './containers/ToDoList';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import reducer from './reducers/common';
const store = createStore(reducer)
ReactDOM.render(
<Provider store={store}>
<ToDoList />
</Provider>,
document.getElementById('root')
);
PS:
<Provider store>
使組件層級中的connect()
方法能夠獲得Redux store。這是React redux中的一個重要的組成部分。在還沒有使用React redux之前,我們需要通過store.subscirbe
對數據進行監控,但使用React redux則通過Provider
和connect()
將React和Redux聯繫起來。在我看來,它就像一座橋樑,更加的方便了我們使用Redux。
在剛開始接觸的時候,一直不理解爲什麼單獨使用Redux也可以,可是大部分人都會選擇React redux呢?之後在實踐的過程中才慢慢理解了這點。
此處寫demo的時候還遇到一個坑,就是Provider包裹的地方,應與container的文件產生聯繫,因爲container就是通過connetc將redeux和react聯繫起來。一開始我錯誤的將view包裹在裏面,卻發現似乎connect的文件一直沒有生效。。。思考了很久之後纔想明白了這件事。
接下來,我開始了編寫了views文件夾裏的ToDoList.js文件
// views ToDoList.js
import React, { useState, useEffect } from 'react';
function ToDoList(props) {
const { inputValue, getInputVal, addItem, list } = props;
const [toDoList, setToDoList] = useState([]);
useEffect(() => {
setToDoList(list);
}, [list]);
return (
<div className="container">
<h3>To Do List</h3>
<div className="input">
<input
type="textarea"
placeholder={inputValue}
onChange={getInputVal}
value={inputValue}
/>
<button onClick={addItem}>添加</button>
</div>
<div className="list">
<ul>
{toDoList &&
toDoList.map((item, index) => {
return <li key={index}>{item}</li>;
})}
</ul>
</div>
</div>
);
}
export default ToDoList;
這裏是demo的所有代碼,下面將會對裏面的一些函數和變量進行講解。
props
在平常的父子組件當中,主要是用做傳遞父組件的數據。而在這裏,則是和container容器中對應的文件聯繫起來。
// containers ToDoList.js
import { connect } from 'react-redux';
import ToDoList from '../views/ToDoList'
import { getInputAction, addItemAction } from '../reducers/actionCreator/toDoList'
const mapStateToProps = state => {
console.log(state)
return {
inputValue: state.inputValue,
list: state.list
};
}
const mapDispatchToPros = dispatch => {
return {
getInputVal(e) {
console.log(e.target.value);
const action = getInputAction(e.target.value)
dispatch(action)
},
addItem() {
const action = addItemAction()
dispatch(action)
}
}
}
export default connect(
mapStateToProps,
mapDispatchToPros
)(ToDoList)
這個容器裏面,就是通過connect將視圖層和redux聯繫起來。
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
mapStateToProps(state, ownProps) : stateProps
這個函數允許我們將store中的數據作爲props綁定到組件上, state則是可以獲取store當中的變量。
mapDispatchToProps(dispatch, ownProps): dispatchProps
這個函數可以將action作爲props綁定到組件上,而需要出發action則是通過dispatch
進行分發。
最後,看看reducers裏面的文件
// actionType toDoList.js
export const GET_INPUTVAL = 'getInputVal'
export const ADD_ITEM = 'addItem'
// actionCreator toDoList.js
import { GET_INPUTVAL, ADD_ITEM } from '../actionType/toDoList'
export const getInputAction = (value) => ({
type: GET_INPUTVAL,
value
})
export const addItemAction = () => ({
type: ADD_ITEM
})
// reducers common.js
import { GET_INPUTVAL, ADD_ITEM } from './actionType/toDoList'
const defaultState = {
inputValue: 'input something...',
list: []
}
export default (state = defaultState, action) => {
switch (action.type) {
case GET_INPUTVAL:
// let newState = JSON.parse(JSON.stringify(state))
// newState.inputValue = action.value
// console.log(newState)
// return newState
return Object.assign({}, state, {
inputValue: action.value
})
break
case ADD_ITEM:
console.log('object')
let newState = JSON.parse(JSON.stringify(state))
newState.list.push(newState.inputValue)
newState.inputValue = ''
return newState
break
default:
break;
}
return state
}
在Redux中,有一個重要的原則就是不能直接對state進行改變,state狀態是不可變的,需要改變state則只能通過dispatch分發action,觸發對應的reducer進行改變。
PS:
這裏說個題外話,在編寫reducer的時候,因爲意識到state是不可變的,所以在返回新狀態的時候需要對其進行深拷貝。也因此對深拷貝和淺拷貝產生了疑惑…(之後需要好好學學😢)
通過這個簡單的實例,我對Redux和React-redux有了初步的認識,而在真正的項目當中,往往都不會只有一個reducer這麼簡單,因此學習如何合併多個reducer至關重要。在這裏則需要學習combineReducers
等其它API的進階使用了。