天天看點

Reac.js入門Redux05

知識點

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裡重新擷取資料,這樣元件之間的資料傳遞變得容易很多。
    Reac.js入門Redux05
  • 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

    該如何修改資料,比作告知管理者圖書的存放地點。
    Reac.js入門Redux05

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一欄。
    Reac.js入門Redux05
    Reac.js入門Redux05
Reac.js入門Redux05
  • 檢視到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欄目,如下圖
    Reac.js入門Redux05
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 字段,給按鈕添加點選事件

    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()