redux介紹
css
redux是什麼react
redux是一個獨立專門用於作狀態管理的JS庫(不是react插件庫),它能夠用在react, angular, vue等項目中, 但基本與react配合使用git
做用: 集中式管理react應用中多個組件共享的狀態github
redux工做流程ajax
將會把這個過程比擬成圖書館的一個流程來幫助理解chrome
Action Creator(具體借書的表達) :想借書的人向圖書館管理員說明要借的書的那句話npm
Store(圖書館管理員) :負責整個圖書館的管理。是Redux的核心編程
Reducers(圖書館管理員的小本本) :管理員須要藉助Reducer(圖書館管理員的小本本)來記錄。redux
React Component(借書的人 ) :須要借書的人
借書的人(ReactComponent)說了一句話(Action Creator)向圖書館管理員(Store)借一本書,但是圖書館管理員年紀大了啊記不住啊,便掏出了本身的小本本(Reducers)。看了看知道了那本書有沒有,在哪,怎麼樣。這樣一來管理員就拿到了這本書,再把這本書交給了借書人
翻譯過來就是:組件想要獲取State, 用ActionCreator建立了一個請求交給Store,Store藉助Reducer確認了該State的狀態,Reducer返回給Store一個結果,Store再把這個State轉給組件。
什麼狀況下須要使用redux
整體原則: 能不用就不用, 若是不用比較吃力才考慮使用,某個組件的狀態,須要共享,某個狀態須要在任何地方均可以拿到
一個組件須要改變全局狀態,一個組件須要改變另外一個組件的狀態
不用redux的方式實現更改狀態
首先咱們建立一個項目,建立應用目錄和文件以下
將建立的main.js組件在App.js入口組件中引入
import React from 'react'; import './App.css'; import Main from './views/main/main' function App() { return ( <div className="App"> <Main/> </div> ); } export default App;
而且在main.js這個組件中實現以下的組件
import React from 'react'; import './main.css'; class Main extends React.Component{ state = { count: 0 } increment = () => { // 1. 讀取select中的值(選擇增長的數量) const number = this.select.value*1 // 由於的到的是字符串因此乘以1隱式轉換成number // 2. 讀取本來state中的count狀態,而且計算新的count const count = this.state.count + number // 3. 更新state的count狀態 this.setState({ count // 完整的寫法式count: count,由於名字相同因此直接寫一個count便可 }) } decrement = () => { // 1. 讀取select中的值(選擇增長的數量) const number = this.select.value*1 // 2. 讀取本來state中的count狀態,而且計算新的count const count = this.state.count - number // 3. 更新state的count狀態 this.setState({ count }) } incrementIfOdd = ()=> { // 1. 讀取select中的值(選擇增長的數量) const number = this.select.value*1 // 2. 讀取本來state中的count狀態 const count = this.state.count // 3. 判斷當前狀態若是式奇數才更新 if (count%2 === 1) { // 4. 更新state的count狀態 this.setState({ count: count + number }) } } incrementAsync =() => { // 1. 讀取select中的值(選擇增長的數量) const number = this.select.value*1 // 2. 讀取本來state中的count狀態 const count = this.state.count // 啓動延時定時器 setTimeout(() => { // 3. 異步更新state的count狀態 this.setState({ count: count + number }) },1000) } render() { const {count} = this.state return ( <div className="App"> <p>click {count} times</p> <div> <select ref={select => this.select = select}> <option value='1'>1</option> <option value='2'>2</option> <option value='3'>3</option> </select> <button onClick={this.increment}>+</button> <button onClick={this.decrement}>-</button> <button onClick={this.incrementIfOdd}>increment if odd</button> <button onClick={this.incrementAsync}>increment async</button> </div> </div> ); } } export default Main;
以上咱們使用正常的方式實現了上圖中的效果,接下來改形成redux的方式去實現,先來看看redux的核心API
redux的核心API
store
就是保存數據的地方,你能夠把它當作一個數據,整個應用智能有一個store,Redux提供createStore這個函數,用來生成Store
state
就是store裏面存儲的數據,store裏面能夠擁有多個state,Redux規定一個state對應一個View,只要state相同,view就是同樣的,反過來也是同樣的,能夠經過store.getState( )獲取
state的改變會致使View的變化,可是在redux中不能直接操做state也就是說不能使用this.setState來操做,用戶只能接觸到View。在Redux中提供了一個對象來告訴Store須要改變state。
Action是一個對象其中type屬性是必須的,表示Action的名稱,其餘的能夠根據需求自由設置。
store.dispatch( )是view觸發Action的惟一辦法,store.dispatch接收一個Action做爲參數,將它發送給store通知store來改變state。
Store收到Action之後,必須給出一個新的state,這樣view纔會發生變化。這種state的計算過程就叫作Reducer。Reducer是一個純函數,他接收Action和當前state做爲參數,返回一個新的state
redux的基本使用
首先下載安裝redux的依賴包
npm install --save redux
項目根目錄建立一個專門管理redux的文件夾,而且建立action-types.js文件,用於管理action對象的type常量名稱模塊
// action對象的type常量名稱模塊 export const INCREMENT = 'increment' export const DECREMENT = 'decrement'
而後再redux目錄中建立reducer函數的模塊:reducers.js
/*包含n個reducer函數的模塊*/ // 根據老的state和指定action, 處理返回一個新的state import {INCREMENT, DECREMENT} from './action-types' export function counter(state = 0, action) { switch (action.type) { case INCREMENT: return state + action.data case DECREMENT: return state - action.data default: return state } }
而後在項目入口文件index.js中引入這個reducer函數counter,而且將這個counter放到createStore方法中返回一個store對象
store對象的做用是redux庫最核心的管理對象,它內部維護着state和reducer,核心方法有getState(),dispatch(action),subscribe(listener)
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import * as serviceWorker from './serviceWorker'; import {createStore} from 'redux'; import {counter} from './redux/reducers'; // 建立一個store對象 // createStore()的做用是建立包含指定reducer的store對象 const store = createStore(counter); // 內部會第一次調用reduer函數獲得初始state console.log(store);//store對象 ReactDOM.render(<App store={store} />, document.getElementById('root')); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.unregister();
而後在應用組件中使用(須要將store經過props傳遞到須要使用的組件中)
import React from 'react'; import './App.css'; import PropTypes from 'prop-types' import Main from './views/main/main' class App extends React.Component { static propTypes = { store: PropTypes.object.isRequired, } render() { return ( <div className="App"> <Main store={this.props.store}/> </div> ); } } export default App;
import React from 'react'; import './main.css'; import PropTypes from "prop-types"; import {INCREMENT, DECREMENT} from '../../redux/action-types' class Main extends React.Component{ static propTypes = { store: PropTypes.object.isRequired, } increment = () => { // 1. 讀取select中的值(選擇增長的數量) const number = this.select.value*1 // 由於的到的是字符串因此乘以1隱式轉換成number // 2. 調用store的方法更新狀態 this.props.store.dispatch({ type: INCREMENT, data: number }) } decrement = () => { // 1. 讀取select中的值(選擇增長的數量) const number = this.select.value*1 // 2. 調用store的方法更新狀態 this.props.store.dispatch({ type: DECREMENT, data: number }) } incrementIfOdd = ()=> { // 1. 讀取select中的值(選擇增長的數量) const number = this.select.value*1 // 2. 讀取本來state中的count狀態 const count = this.props.store.getState() // 3. 判斷當前狀態若是式奇數才更新 if (count%2 === 1) { // 4. 調用store的方法更新狀態 this.props.store.dispatch({ type: INCREMENT, data: number }) } } incrementAsync =() => { // 1. 讀取select中的值(選擇增長的數量) const number = this.select.value*1// 啓動延時定時器 setTimeout(() => { // 2. 調用store的方法更新狀態 this.props.store.dispatch({ type: INCREMENT, data: number }) },1000) } render() { const count = this.props.store.getState() return ( <div className="App"> <p>click {count} times</p> <div> <select ref={select => this.select = select}> <option value='1'>1</option> <option value='2'>2</option> <option value='3'>3</option> </select> <button onClick={this.increment}>+</button> <button onClick={this.decrement}>-</button> <button onClick={this.incrementIfOdd}>increment if odd</button> <button onClick={this.incrementAsync}>increment async</button> </div> </div> ); } } export default Main;
subscribe(listener),狀態更新了以後須要調用這個方法來刷新
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import * as serviceWorker from './serviceWorker'; import {createStore} from 'redux'; import {counter} from './redux/reducers'; // 建立一個store對象 // createStore()的做用是建立包含指定reducer的store對象 const store = createStore(counter); // 內部會第一次調用reduer函數獲得初始state console.log(store);//store對象 function render () { ReactDOM.render(<App store={store} />, document.getElementById('root')); } render() // 初始化渲染 store.subscribe(render) // 訂閱監聽(store中的狀態變化了就會自動調用進行重繪) // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.unregister();
能夠把要執行的行爲對象action單獨抽離出來一個模塊,使用工廠函數的方式(官方推薦的寫法),在redux文件夾中建立一個actions.js
/*action creator模塊*/ import {INCREMENT, DECREMENT} from './action-types' export const increment = number => ({type: INCREMENT, data: number}) export const decrement = number => ({type: DECREMENT, data: number})
import React from 'react'; import './main.css'; import PropTypes from "prop-types"; import * as actions from '../../redux/actions' class Main extends React.Component{ static propTypes = { store: PropTypes.object.isRequired, } increment = () => { // 1. 讀取select中的值(選擇增長的數量) const number = this.select.value*1 // 由於的到的是字符串因此乘以1隱式轉換成number // 2. 調用store的方法更新狀態 this.props.store.dispatch(actions.increment(number)) } decrement = () => { // 1. 讀取select中的值(選擇增長的數量) const number = this.select.value*1 // 2. 調用store的方法更新狀態 this.props.store.dispatch(actions.decrement(number)) } incrementIfOdd = ()=> { // 1. 讀取select中的值(選擇增長的數量) const number = this.select.value*1 // 2. 讀取本來state中的count狀態 const count = this.props.store.getState() // 3. 判斷當前狀態若是式奇數才更新 if (count%2 === 1) { // 4. 調用store的方法更新狀態 this.props.store.dispatch(actions.increment(number)) } } incrementAsync =() => { // 1. 讀取select中的值(選擇增長的數量) const number = this.select.value*1 // 啓動延時定時器 setTimeout(() => { // 2. 調用store的方法更新狀態 this.props.store.dispatch(actions.increment(number)) },1000) } render() { const count = this.props.store.getState() return ( <div className="App"> <p>click {count} times</p> <div> <select ref={select => this.select = select}> <option value='1'>1</option> <option value='2'>2</option> <option value='3'>3</option> </select> <button onClick={this.increment}>+</button> <button onClick={this.decrement}>-</button> <button onClick={this.incrementIfOdd}>increment if odd</button> <button onClick={this.incrementAsync}>increment async</button> </div> </div> ); } } export default Main;
還有一個就是將store搬到一個獨立的模塊中,在redux文件夾建立一個store.js的文件
import {createStore} from 'redux'; import {counter} from './reducers'; // 建立一個store對象 // createStore()的做用是建立包含指定reducer的store對象 const store = createStore(counter); // 內部會第一次調用reduer函數獲得初始state console.log(store);//store對象 export default store
修改index.js項目入口文件
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import * as serviceWorker from './serviceWorker'; import store from './redux/store' function render () { ReactDOM.render(<App store={store} />, document.getElementById('root')); } render() // 初始化渲染 store.subscribe(render) // 訂閱監聽(store中的狀態變化了就會自動調用進行重繪) // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.unregister();
react-redux
上面使用redux,有一些小的問題就是:redux與react組件的代碼耦合度過高,編碼不夠簡潔,那麼就有了一個插件react-redux
react-redux:一個react插件庫,專門用來簡化react應用中使用redux
React-Redux將全部組件分紅兩大類:
UI組件:只負責 UI 的呈現,不帶有任何業務邏輯,經過props接收數據(通常數據和函數),不使用任何 Redux 的 API,通常保存在components文件夾下
容器組件:負責管理數據和業務邏輯,不負責UI的呈現,使用 Redux 的 API,通常保存在containers文件夾下
React-Redux相關API
Provider:這是一個組件,將這個組件包裹着App.js組件讓全部組件均可以獲得state數據
<Provider store={store}>
<App />
</Provider>
connect():用於包裝 UI 組件生成容器組件,就是用於react組件和redux之間的鏈接
mapStateToprops():將外部的數據(即state對象)轉換爲UI組件的標籤屬性
mapDispatchToProps():將分發action的函數轉換爲UI組件的標籤屬性,簡潔語法能夠直接指定爲actions對象或包含多個action方法的對象
import { connect } from 'react-redux'
connect(
mapStateToprops,
mapDispatchToProps
)(Counter)
使用react-redux
首先須要下載安裝依賴包
npm install --save react-redux
修改index.js項目入口文件
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import * as serviceWorker from './serviceWorker'; import store from './redux/store' import {Provider} from 'react-redux' ReactDOM.render(( <Provider store={store} > <App/> </Provider> ), document.getElementById('root')); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.unregister();
sotre也不須要一層層的傳遞了
import React from 'react'; import './App.css'; import Main from './views/main/main' class App extends React.Component { render() { return ( <div className="App"> <Main/> </div> ); } } export default App;
在應用組件中使用react-redux的API方法connect()來跟store進行鏈接
import React from 'react'; import './main.css'; import PropTypes from "prop-types"; import { connect } from 'react-redux'; import {increment, decrement} from '../../redux/actions'; class Main extends React.Component{ static propTypes = { count: PropTypes.number.isRequired, increment: PropTypes.func.isRequired, decrement: PropTypes.func.isRequired } increment = () => { // 1. 讀取select中的值(選擇增長的數量) const number = this.select.value*1 // 由於的到的是字符串因此乘以1隱式轉換成number // 2. 調用store的方法更新狀態 this.props.increment(number) } decrement = () => { // 1. 讀取select中的值(選擇增長的數量) const number = this.select.value*1 // 2. 調用store的方法更新狀態 this.props.decrement(number) } incrementIfOdd = ()=> { // 1. 讀取select中的值(選擇增長的數量) const number = this.select.value*1 // 2. 讀取本來state中的count狀態 const count = this.props.count // 3. 判斷當前狀態若是式奇數才更新 if (count%2 === 1) { // 4. 調用store的方法更新狀態 this.props.increment(number) } } incrementAsync =() => { // 1. 讀取select中的值(選擇增長的數量) const number = this.select.value*1 // 啓動延時定時器 setTimeout(() => { // 2. 調用store的方法更新狀態 this.props.increment(number) },1000) } render() { const {count} = this.props return ( <div className="App"> <p>click {count} times</p> <div> <select ref={select => this.select = select}> <option value='1'>1</option> <option value='2'>2</option> <option value='3'>3</option> </select> <button onClick={this.increment}>+</button> <button onClick={this.decrement}>-</button> <button onClick={this.incrementIfOdd}>increment if odd</button> <button onClick={this.incrementAsync}>increment async</button> </div> </div> ); } } // connect是一個函數須要接收一個參數是組件類型(也就是一個對象),執行以後返回的仍是一個函數,而且返回新的組件 export default connect( state => ({count: state}), {increment, decrement} )(Main);
這樣咱們就把react-redux給使用上了,還能夠在進一步優化就是將connect和組件分離出來,UI組件: 不包含任何redux API
import React from 'react'; import './main.css'; import PropTypes from "prop-types"; class Main extends React.Component{ static propTypes = { count: PropTypes.number.isRequired, increment: PropTypes.func.isRequired, decrement: PropTypes.func.isRequired } increment = () => { // 1. 讀取select中的值(選擇增長的數量) const number = this.select.value*1 // 由於的到的是字符串因此乘以1隱式轉換成number // 2. 調用store的方法更新狀態 this.props.increment(number) } decrement = () => { // 1. 讀取select中的值(選擇增長的數量) const number = this.select.value*1 // 2. 調用store的方法更新狀態 this.props.decrement(number) } incrementIfOdd = ()=> { // 1. 讀取select中的值(選擇增長的數量) const number = this.select.value*1 // 2. 讀取本來state中的count狀態 const count = this.props.count // 3. 判斷當前狀態若是式奇數才更新 if (count%2 === 1) { // 4. 調用store的方法更新狀態 this.props.increment(number) } } incrementAsync =() => { // 1. 讀取select中的值(選擇增長的數量) const number = this.select.value*1 // 啓動延時定時器 setTimeout(() => { // 2. 調用store的方法更新狀態 this.props.increment(number) },1000) } render() { const {count} = this.props return ( <div className="App"> <p>click {count} times</p> <div> <select ref={select => this.select = select}> <option value='1'>1</option> <option value='2'>2</option> <option value='3'>3</option> </select> <button onClick={this.increment}>+</button> <button onClick={this.decrement}>-</button> <button onClick={this.incrementIfOdd}>increment if odd</button> <button onClick={this.incrementAsync}>increment async</button> </div> </div> ); } } export default Main
建立containters文件夾而且建立一個js文件(包含Main組件的容器組件)
import React from 'react'; import { connect } from 'react-redux'; import {increment, decrement} from '../redux/actions'; import Main from '../views/main/main' // connect是一個函數須要接收一個參數是組件類型(也就是一個對象),執行以後返回的仍是一個函數,而且返回新的組件 export default connect( state => ({count: state}), {increment, decrement} )(Main);
import React from 'react'; import './App.css'; import Main from './containters/main' class App extends React.Component { render() { return ( <div className="App"> <Main/> </div> ); } } export default App;
這樣就完成了react-redux的使用,可是也有一個問題就是redux默認是不能進行異步處理的, 應用中又須要在redux中執行異步任務(ajax, 定時器)
怎麼樣讓redux支持異步操做,請看下面:redux異步編程
redux異步編程
下載redux插件(異步中間件)
npm install --save redux-thunk
而後下redux文件夾的store.js文件中引入
import {createStore, applyMiddleware} from 'redux'; import thunk from 'redux-thunk'; import {counter} from './reducers'; // 建立一個store對象 // createStore()的做用是建立包含指定reducer的store對象 const store = createStore( counter, applyMiddleware(thunk) // 應用上異步中間件 ); export default store
在actions.js中添加一個異步操做的方法
/*action creator模塊*/ import {INCREMENT, DECREMENT} from './action-types' export const increment = number => ({type: INCREMENT, data: number}) export const decrement = number => ({type: DECREMENT, data: number}) export const incrementAsync = number => { return dispatch => { setTimeout(() => { dispatch(increment(number)) }, 1000) } }
在connect()把這個action方法加進來
import React from 'react'; import { connect } from 'react-redux'; import {increment, decrement, incrementAsync} from '../redux/actions'; import Main from '../views/main/main' // connect是一個函數須要接收一個參數是組件類型(也就是一個對象),執行以後返回的仍是一個函數,而且返回新的組件 export default connect( state => ({count: state}), {increment, decrement, incrementAsync} )(Main);
在應用組件中經過props進行調用這個action方法
import React from 'react'; import './main.css'; import PropTypes from "prop-types"; class Main extends React.Component{ static propTypes = { count: PropTypes.number.isRequired, increment: PropTypes.func.isRequired, decrement: PropTypes.func.isRequired, incrementAsync: PropTypes.func.isRequired } increment = () => { // 1. 讀取select中的值(選擇增長的數量) const number = this.select.value*1 // 由於的到的是字符串因此乘以1隱式轉換成number // 2. 調用store的方法更新狀態 this.props.increment(number) } decrement = () => { // 1. 讀取select中的值(選擇增長的數量) const number = this.select.value*1 // 2. 調用store的方法更新狀態 this.props.decrement(number) } incrementIfOdd = ()=> { // 1. 讀取select中的值(選擇增長的數量) const number = this.select.value*1 // 2. 讀取本來state中的count狀態 const count = this.props.count // 3. 判斷當前狀態若是式奇數才更新 if (count%2 === 1) { // 4. 調用store的方法更新狀態 this.props.increment(number) } } incrementAsync =() => { // 1. 讀取select中的值(選擇增長的數量) const number = this.select.value*1 // 2. 調用store的方法更新狀態 this.props.incrementAsync(number) } render() { const {count} = this.props return ( <div className="App"> <p>click {count} times</p> <div> <select ref={select => this.select = select}> <option value='1'>1</option> <option value='2'>2</option> <option value='3'>3</option> </select> <button onClick={this.increment}>+</button> <button onClick={this.decrement}>-</button> <button onClick={this.incrementIfOdd}>increment if odd</button> <button onClick={this.incrementAsync}>increment async</button> </div> </div> ); } } export default Main
使用上redux調試工具
首先安裝chrome瀏覽器插件(直接在谷歌應用商店搜索,而後點擊安裝)
而後在項目中下載工具依賴包
npm install --save-dev redux-devtools-extension
修改項目中的建立store的代碼中加入這個插件
import {createStore, applyMiddleware} from 'redux'; import thunk from 'redux-thunk'; import { composeWithDevTools } from 'redux-devtools-extension' import {counter} from './reducers'; // 建立一個store對象 // createStore()的做用是建立包含指定reducer的store對象 const store = createStore( counter, composeWithDevTools(applyMiddleware(thunk)) // 應用上異步中間件 ); export default store
let createStore = (reducer) => { let state; //獲取狀態對象 //存放全部的監聽函數 let listeners = []; let getState = () => state; //提供一個方法供外部調用派發action let dispath = (action) => { //調用管理員reducer獲得新的state state = reducer(state, action); //執行全部的監聽函數 listeners.forEach((l) => l()) } //訂閱狀態變化事件,當狀態改變發生以後執行監聽函數 let subscribe = (listener) => { listeners.push(listener); } dispath(); return { getState, dispath, subscribe } } let combineReducers=(renducers)=>{ //傳入一個renducers管理組,返回的是一個renducer return function(state={},action={}){ let newState={}; for(var attr in renducers){ newState[attr]=renducers[attr](state[attr],action) } return newState; } } export {createStore,combineReducers};