React之Redux

知識點

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.jsreducer.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()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章