知識點
1、Redux概念簡述
2、Redux的工作流程
3、使用Antd實現TodoList頁面佈局
4、創建Redux中的store
5、Action和Reducer的編寫
6、使用Redux完成TodoList刪除功能
7、ActionTypes的拆分
8、使用actionCreator統一創建action
9、Redux知識點複習補充
1、Redux概念簡述
- 由於React是一個視圖層框架,如果需要進行跨層組件之間的通信,就會很複雜,很多代碼變得不可維護,所以使用React來開發時,一般需要配合使用一個數據層框架,來輔助組件間的通信。
- Redux是一個數據層框架,它通過將所有數據存儲一個公用store空間裏,而不把數據放在組件自身。如果一個組件修改數據,則只需要修改store裏面的數據,然後其他組件會自動感知到store的變化,然後就從store裏重新獲取數據,這樣組件之間的數據傳遞變得容易很多。
- Redux = Reducer + Flux,其中Flux是Facbook推出的最原始的輔助React的數據層框架,但是有一些不足,在公共存儲區域Store可以由多個區域Store組成,這樣存儲時可能存在數據依賴問題,因此經過升級後得到Redux。Redux除了借鑑Flux的設計理念,還引入了Reducer的概念。
2、Redux的工作流程
- React Components指頁面上的組件,比作圖書館的借書人;
- Action Creators指想要改變數據的請求,比作借書的請求;
- Store指存儲數據的公共區域,比作圖書館管理員;
- React Components告知管理員Store有想要修改數據的意願Action Creators;
- Reducers指如何修改數據的方法,比作圖書館書籍存放的記錄本;
- Reducers告知Store該如何修改數據,比作告知管理員圖書的存放地點。
3、使用Antd實現TodoList頁面佈局
1)Antd介紹
官方Antd文檔 https://ant.design/docs/react/introduce-cn
簡介: Antd 是基於 Ant Design 設計體系的 React UI 組件庫,主要用於研發企業級中後臺產品。
特性:
- 提煉自企業級中後臺產品的交互語言和視覺風格。
- 開箱即用的高質量 React 組件。
- 使用 TypeScript 構建,提供完整的類型定義文件。
- 全鏈路開發和設計工具體系。
支持環境: 現代瀏覽器和 IE9 及以上(需要 polyfills);支持服務端渲染;Electron。
2)Antd的引入
安裝Antd包,打開cmd,輸入以下命令行。安裝完後,重啓服務器。
cd <項目目錄下>
$ npm install antd --save
$ npm run start
3)使用Antd佈局TodoList
TodoList.js(重寫TodoList)
import React, {
Component
} from 'react';
import 'antd/dist/antd.css';
import {
Input,
Button,
List
} from 'antd';
// 使用Antd實現TodoList佈局
const data = [
'Racing car sprays burning fuel into crowd.',
'Japanese princess to wed commoner.',
'Australian walks 100km after outback crash.',
'Man charged over missing wedding girl.',
'Los Angeles battles huge wildfires.',
];
class TodoList extends Component {
render() {
return (
<div style={{margin:'10px'}}>
<div>
<Input placeholder='todo info' style={{width:'300px',marginRight:'10px'}} />
<Button type="primary">提交</Button>
</div>
<List
style={{width:'300px',marginTop:'10px'}}
bordered
dataSource={data}
renderItem={item => (<List.Item>{item}</List.Item>)}
/>
</div>
)
}
}
export default TodoList;
4、創建Redux中的store
官網redux文檔 https://redux.js.org/introduction/getting-started
創建一個store進行數據管理,比作圖書館管理員
1)安裝redux
安裝Redux包,打開cmd,輸入以下命令行。安裝完後,重啓服務器。
cd <項目目錄下>
$ npm install redux --save
$ npm run start
2)創建並引入store
- 在項目learn-react -> src文件夾下,創建一個store文件夾,並且創建兩個js文件,分別爲index.js和reducer.js。
- index.js表示一個數據管理員;
- reducer.js表示一個數據記錄本;
- TodoList.js跟管理員store獲取數據this.state = store.getState();
src/store/index.js(新建js文件)
import {
createStore
} from 'redux';
import reducer from './reducer'; //引入記事本
// 創建一個圖書管理員store
const store = createStore(reducer);
export default store;
src/store/reducer.js(新建js文件)
// 創建記事本,記錄數據信息
// state存儲整個圖書館的全部信息
const defaultState = {
inputValue: '123',
list: [1, 23, 4]
};
// 返回數據
export default (state = defaultState, action) => {
return state;
}
src/TodoList.js(修改TodoList)
import React, {
Component
} from 'react';
import 'antd/dist/antd.css';
import {
Input,
Button,
List
} from 'antd';
import store from './store'; //引入管理員
// 使用Antd實現TodoList佈局
class TodoList extends Component {
constructor(props) {
super(props);
// 獲取store的數據
this.state = store.getState();
}
render() {
return (
<div style={{marginLeft:'10px'}}>
<div>
<Input value={this.state.inputValue} placeholder='todo info' style={{width:'300px',marginTop:'10px',marginRight:'10px'}} />
<Button type="primary">提交</Button>
</div>
<List
style={{width:'300px',marginTop:'10px'}}
bordered
dataSource={this.state.list}
renderItem={item => (<List.Item>{item}</List.Item>)}
/>
</div>
)
}
}
export default TodoList;
5、Action和Reducer的編寫
1)安裝redux devtools
-
打開翻牆軟件 -> 點擊谷歌瀏覽器右上角三點處 -> 選擇“更多工具 - 擴展程序” -> 點擊“擴展程序”頁面左上角”擴展程序“ -> 點擊展開的側邊欄右下角的“打開Chrome網上應用店” -> 在應用店頁面搜索 " redux devtools " -> 點擊搜索結果 " redux devtools " 的 " 添加至Chrome " -> 添加成功後,可以在頁面右上角看到redux圖標 -> 重啓瀏覽器,就可以在開發者工具看到redux一欄。
-
查看到redux欄目顯示 Not Found,因此需要在src/store/index.js文件中createStore()添加一個參數,具體如下:
src/store/index.js(修改js文件)
...
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
...
修改完後,重新查看redux欄目,如下圖
2)使用redux Flow流程
- 獲取store管理的數據
this.state = store.getState();
- 創建一個修改數據的請求Action Creators,修改Input框的輸入內容,給Input框增加一個change事件;
<Input
value={this.state.inputValue}
placeholder='todo info'
style={{width:'300px',marginTop:'10px',marginRight:'10px'}}
onChange={this.handleInputChange}
/>
- 告知store這個請求dispatch action,store會自動將之前的數據previousState和當前用戶想要的操作action自動轉發給reducer;
handleInputChange(e) {
// 創建一個請求
const action = {
type: 'change_input_value',
value: e.target.value
};
// 告知store這個請求
store.dispatch(action);
}
- reducers進行數據修改,並將修改後的新數據返回給store;
export default (state = defaultState, action) => {
if (action.type === 'change_input_value') {
// 對舊數據進行深拷貝
const newState = JSON.parse(JSON.stringify(state));
// 修改新數據並返回
newState.inputValue = action.value;
return newState;
}
console.log(state, action);
return state;
}
- store會自動地將reducer處理後返回的新數據替換掉舊數據
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
- TodoList組件識別到store的數據變化,就自動更新組件的state數據,將Input框輸入的新內容顯示到框內,到此就完成一個Redux Flow流程。
...
this.handleStoreChange = this.handleStoreChange.bind(this);
// 聲明一個函數,這個函數當store的數據一旦改變,就可自動的執行
store.subscribe(this.handleStoreChange);
...
handleStoreChange() {
//更新組件數據
this.setState(store.getState());
}
...
- Input框輸入內容點擊提交,修改store數據的 list 字段,給按鈕添加點擊事件handleBtnClick(),發起一個action,並告知store。
...
<Button type="primary" onClick={this.handleBtnClick}>提交</Button>
...
handleBtnClick() {
const action = {
type: 'add_todo_item',
}
store.dispatch(action);
}
...
- store得到action請求後,自動將previousState和action傳遞給reducer,reducer進行數據處理後,將新數據newState返回給store。
if (action.type === 'add_todo_item') {
const newState = JSON.parse(JSON.stringify(state));
newState.list.push(newState.inputValue);
newState.inputValue = '';
console.log(newState);
return newState;
}
- store感知到數據變化後,會自動執行handleStoreChange()函數,從而將提交的內容顯示到頁面上。
完整代碼修改
src/TodoList.js(修改js文件)
import React, {
Component
} from 'react';
import 'antd/dist/antd.css';
import {
Input,
Button,
List
} from 'antd';
import store from './store'; //引入管理員
// 使用Antd實現TodoList佈局
class TodoList extends Component {
constructor(props) {
super(props);
// 1、獲取store管理的數據
this.state = store.getState();
this.handleInputChange = this.handleInputChange.bind(this);
this.handleStoreChange = this.handleStoreChange.bind(this);
this.handleBtnClick = this.handleBtnClick.bind(this);
// 6、從store獲取數據,更新組件state數據
// 聲明一個函數,這個函數當store的數據一旦改變,就可自動的執行
store.subscribe(this.handleStoreChange);
}
render() {
return (
<div style={{marginLeft:'10px'}}>
<div>
<Input
value={this.state.inputValue}
placeholder='todo info'
style={{width:'300px',marginTop:'10px',marginRight:'10px'}}
onChange={this.handleInputChange}
/>
<Button
type="primary"
onClick={this.handleBtnClick}
>提交</Button>
</div>
<List
style={{width:'300px',marginTop:'10px'}}
bordered
dataSource={this.state.list}
renderItem={item => (<List.Item>{item}</List.Item>)}
/>
</div>
)
}
handleInputChange(e) {
// 更改store管理的數據
//2、創建一個請求
const action = {
type: 'change_input_value',
value: e.target.value
};
//3、告知store這個請求,store會自動將之前的數據previousState和當前用戶想要的操作action自動轉發給reducer
store.dispatch(action);
}
handleStoreChange() {
//更新組件數據
this.setState(store.getState());
}
// 7、點擊提交input框內容
handleBtnClick() {
const action = {
type: 'add_todo_item',
}
store.dispatch(action);
}
}
export default TodoList;
src/store/index.js(修改js文件)
import {
createStore
} from 'redux';
import reducer from './reducer'; //引入筆記本
// 創建一個圖書管理員store
// 5、store會自動地將reducer處理後返回的新數據替換掉舊數據
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
export default store;
src/store/reducer.js(修改js文件)
// 創建記事本,記錄數據信息
const defaultState = {
inputValue: '123',
list: [1, 23, 4]
};
// 返回數據
// state存儲整個圖書館的全部信息
// reducer可以接受state,但絕不能修改state
export default (state = defaultState, action) => {
// 4、告知store如何處理change_input_value請求
if (action.type === 'change_input_value') {
// 對舊數據進行深拷貝
const newState = JSON.parse(JSON.stringify(state));
// 修改新數據並返回
newState.inputValue = action.value;
return newState;
}
// 8、告知store如何處理add_todo_item請求
if (action.type === 'add_todo_item') {
const newState = JSON.parse(JSON.stringify(state));
newState.list.push(newState.inputValue);
newState.inputValue = '';
console.log(newState);
return newState;
}
return state;
}
6、使用Redux完成TodoList刪除功能
給列表 list 的每一項 item 綁定一個點擊刪除事件
src/TodoList.js(添加代碼)
...
<List
style={{width:'300px',marginTop:'10px'}}
bordered
dataSource={this.state.list}
renderItem={(item,index) => (<List.Item onClick={this.handleItemDelete.bind(this,index)}>{item}</List.Item>)}
/>
...
//刪除列表Item
handleItemDelete(index) {
const action = {
type: 'delete_todo_item',
index
};
store.dispatch(action);
}
...
src/store/reducer.js(添加代碼)
...
if (action.type === 'delete_todo_item') {
const newState = JSON.parse(JSON.stringify(state));
newState.list.splice(action.index, 1);
return newState;
}
...
7、ActionTypes的拆分
如果把action的type的值寫錯,比如TodoList.js文件中把change_input_value寫成change_invt_value,到頁面進行調試,發現控制檯並不會報錯。所以,爲了更好的方便調試,將action的type名抽取出來。因此,在 src / store 文件夾下,新建一個 actionTypes.js 文件,進行記錄action的Type名稱。
修改如下:
src/TodoList.js(修改代碼)
...
import {
CHANGE_INPUT_VALUE,
ADD_TODO_ITEM,
DELETE_TODO_ITEM
} from './store/actionTypes';
...
const action = {
type: CHANGE_INPUT_VALUE,
value: e.target.value
};
...
const action = {
type: ADD_TODO_ITEM,
};
...
const action = {
type: DELETE_TODO_ITEM,
index
};
src/store/reducer.js(修改代碼)
import {
CHANGE_INPUT_VALUE,
ADD_TODO_ITEM,
DELETE_TODO_ITEM
} from './actionTypes';
...
if (action.type === CHANGE_INPUT_VALUE) {...
...
if (action.type === ADD_TODO_ITEM) {...
...
if (action.type === DELETE_TODO_ITEM) {...
...
src/store/actionTypes.js(新建js文件)
//導出常量
export const CHANGE_INPUT_VALUE = 'change_input_value';
export const ADD_TODO_ITEM = 'add_todo_item';
export const DELETE_TODO_ITEM = 'delete_todo_item';
8、使用actionCreator統一創建action
爲了便於管理action,將所有action放到actionCreator進行統一管理。作用提高代碼的可維護性,方便前端做代碼的自動化測試。因此,在 src / store 文件夾下,新建一個 actionCreators.js 文件,統一管理action。
代碼修改如下
src/TodoList.js(修改代碼)
...
// import {
// CHANGE_INPUT_VALUE,
// ADD_TODO_ITEM,
// DELETE_TODO_ITEM
// } from './store/actionTypes';
//刪除以上註釋代碼,修改爲以下代碼
import {
getInputChangeAction,
getAddItemAction,
getDeleteItemAction
} from './store/actionCreators';
...
//const action = {
// type: CHANGE_INPUT_VALUE,
// value: e.target.value
//};
//刪除以上註釋代碼,修改爲以下代碼
const action = getInputChangeAction(e.target.value);
...
//const action = {
// type: ADD_TODO_ITEM,
//};
//刪除以上註釋代碼,修改爲以下代碼
const action = getAddItemAction();
...
//const action = {
// type: DELETE_TODO_ITEM,
// index
//};
//刪除以上註釋代碼,修改爲以下代碼
const action = getDeleteItemAction(index);
src/store/actionCreators.js(新建js文件)
import {
CHANGE_INPUT_VALUE,
ADD_TODO_ITEM,
DELETE_TODO_ITEM
} from './actionTypes';
//返回一個js對象
export const getInputChangeAction = (value) => ({
type: CHANGE_INPUT_VALUE,
value
});
export const getAddItemAction = (value) => ({
type: ADD_TODO_ITEM
});
export const getDeleteItemAction = (index) => ({
type: DELETE_TODO_ITEM,
index
});
9、Redux知識點複習補充
1)Redux設計和使用的三項原則
- store是唯一的
- 只有 store 能夠改變自己的內容。表面上看,像 reducer 在修改 state 數據,但實際上,reducer 經過處理數據之後,將新的 newState 返回給 store ,store收到 reducer 返回的新newState,然後對自己的舊數據進行更新,而 reducer 並不能更改 store 的數據。所以,不能在 reducer 中直接寫 state.list = …,因爲這樣就在 reducer 中直接更改了store的數據,所以 reducer 在進行數據修改時,必須要先深拷貝一份新數據出來進行修改。
- Reducer 必須是一個純函數。純函數,指的是給定固定的輸入,就一定會有固定的輸出,而且不會有任何副作用。一旦一個函數內部有new Date()、setTimeout()、ajax()等函數,這些函數會因時間變化而內容變化,那麼這個函數就不是純函數。加入在這個函數內部加入state.inputValue=action.value;,這句話會造成修改store的數據,一旦修改了,那麼就說明帶來了額外的副作用,那麼這個函數就不是純函數。
2)Redux核心API
- createStore()
- store.dispatch()
- store.getState()
- store.subscribe()