知識點
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
-
跟管理者store擷取資料TodoList.js
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檔案)
修改完後,重新檢視redux欄目,如下圖... const store = createStore( reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() ); ...
2)使用redux Flow流程
- 擷取store管理的資料
- 建立一個修改資料的請求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 字段,給按鈕添加點選事件
,發起一個action,并告知store。handleBtnClick()
... <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 中直接寫
,因為這樣就在 reducer 中直接更改了store的資料,是以 reducer 在進行資料修改時,必須要先深拷貝一份新資料出來進行修改。state.list = ...
- Reducer 必須是一個純函數。純函數,指的是給定固定的輸入,就一定會有固定的輸出,而且不會有任何副作用。一旦一個函數内部有
等函數,這些函數會因時間變化而内容變化,那麼這個函數就不是純函數。加入在這個函數内部加入new Date()、setTimeout()、ajax()
,這句話會造成修改store的資料,一旦修改了,那麼就說明帶來了額外的副作用,那麼這個函數就不是純函數。state.inputValue=action.value;
2)Redux核心API
- createStore()
- store.dispatch()
- store.getState()
- store.subscribe()