Redux是什麼javascript
不少人認爲redux必需要結合React使用,其實並非的,Redux 是 JavaScript 狀態容器,只要你的項目中使用到了狀態,而且狀態十分複雜,那麼你就可使用Redux管理你的項目狀態,它可使用在react中,也可使用中在Vue中,固然也適用其餘的框架。
render()
方法,從而改變視圖固然,如今可能看不懂這在瞎說啥,可是等把這篇文章看完再來這個圖,和這段話,就會有恍然大明白的感受react
action本質上就是一個對象,它必定有一個名爲type
的key 如{type: 'add'}
,{type: 'add'}
就是一個action
可是咱們只實際工做中並非直接用action ,而是使用action建立函數,(千萬別弄混淆),
顧名思義action建立函數就是一個函數,它的做用就是返回一個action,如:es6
function add() { return {type: 'add'} }
reducer其實就是一個函數,它接收兩個參數,第一個參數是須要管理的狀態state,第二個是action。reducer會根據傳入的action的type值對state進行不一樣的操做,而後返回一個新的state,而不是在原有state的基礎上進行修改,可是若是遇到了未知的(不匹配的)action,就會返回原有的state,不進行任何改變。redux
function reducer(state = {money: 0}, action) { //返回一個新的state可使用es6提供的Object.assign()方法,或擴展運算符(此方法須要babel-preset-state-3支持) switch (action.type) { case '+': return Object.assign({}, state, {money: state.money + 1}); case '-': return {...state, ...{money: state.money - 1}}; default: return state; } }
你能夠把store想成一個狀態樹,它包含了整個redeux應用的全部狀態。
咱們使用redux提供的createStore
方法生成storebabel
import {createStore} from 'redux'; const store = createStore(reducer);
store提供了幾個方法供咱們使用,下面是咱們經常使用的3個:網絡
store.getState();//獲取整個狀態樹 store.dispatch();//改變狀態,改變state的惟一方法 store.subscribe();//訂閱一個函數,每當state改變時,都會去調用這個函數
接下來演示一個redux的完整應用,而且說明這三個方法該怎麼用app
import {createStore} from 'redux'; //給初始狀態一個默認值:{money: 0} function reducer(state = {money: 0}, action) { //返回一個新的state可使用es6提供的Object.assign()方法,或擴展運算符(此方法須要babel-preset-state-3支持) switch (action.type) { case '+': return Object.assign({}, state, {money: state.money + 1}); case '-': return {...state, ...{money: state.money - 1}}; default: return state; } } //action建立函數,返回了一個action function add() { return {type: '+'} } function subtraction() { return {type: '-'} } //建立單一狀態樹 const store = createStore(reducer); console.log(store.getState());//{money: 0},初始的狀態,沒有任何改變(經過getState來獲取目前的狀態) //store經過dispatch這個方法,而且傳入action做爲參數,對store進行了改變 store.dispatch(add()); console.log(store.getState());//{money: 1},reducer接受到了 '+' 這個命令,就撿到了一塊錢 store.dispatch(subtraction()); console.log(store.getState());//{money: 0},reducer接受到了 '-' 這個命令,又掉了一塊錢 store.dispatch({type:'我是來搗亂的'}); console.log(store.getState());//{money: 0},reducer接受到了一個不識別命令,返回原有的state
這個時候咱們就會發現幾個問題:框架
console.log()
才能知道改變後的狀態,這個時候咱們就可使用store.subscribe()
來訂閱一個事件,代替咱們在每次dispatch
後都要console.log()
後才能知道改變後的狀態dom
function listen() { console.log(store.getState()); } store.subscribe(listen);
將type維護成常量,這樣咱們在往後的維護過程當中只須要對常量進行維護就能夠了,咱們目前這個demo使用到type的地方太少可能感受不到,但是在實際項目中這個方法卻很是的實用
const ADD = '+', SUBTRACTION = '-';
咱們優化後的代碼以下:
import {createStore} from 'redux'; //定義常量方便維護 const ADD = '+', SUBTRACTION = '-'; //給初始狀態一個默認值:{money: 0} function reducer(state = {money: 0}, action) { //返回一個新的state可使用es6提供的Object.assign()方法,或擴展運算符(此方法須要babel-preset-state-3支持) switch (action.type) { case ADD: return Object.assign({}, state, {money: state.money + 1}); case SUBTRACTION: return {...state, ...{money: state.money - 1}}; default: return state; } } //action建立函數,返回了一個action function add() { return {type: ADD} } function subtraction() { return {type: SUBTRACTION} } //打印改變後的狀態 function listen() { console.log(store.getState()); } //建立單一狀態樹 const store = createStore(reducer); //訂閱listen,每次dispatch後都會執行listen,從而打印狀態(只有在執行dispatch後纔會執行,狀態初始化的時候並不會執行) store.subscribe(listen); console.log(store.getState());//初始的狀態,沒有任何改變 //store經過dispatch這個方法,而且傳入action做爲參數,對store進行了改變 store.dispatch(add()); store.dispatch(subtraction()); store.dispatch({type: '我是來搗亂的'}); /*控制檯的打印結果以下: {money: 0} {money: 1} {money: 0} {money: 0}*/
補充:
一個應用只能有一個store,這個時候就會有一個問題 ,若是有多個reducer分別來處理不一樣的狀態,而createStore是能接受一個reducer,這個時候咱們就須要redux提供的combineReducers
方法來將多個reducer結合成一個reducer
import {combineReducers} from 'redux'; const reducerFamily=combineReducers({ reduceSon, reduceDaughter, reducerFather, reducerMother }) const store = createStore(reducerFamily);
若是會react,那麼也必定知道creact-react-app這個官方腳手架工具,首先使用creact-react-app建立一個項目,而後刪除src目錄下全部文件,接下來就能夠愉快的敲代碼了。
在src下建立三個文件
index.js
import React from 'react' import ReactDOM from 'react-dom' import {createStore} from 'redux' //引入咱們的reducer和action建立函數 import {reducer, add, subtraction} from './index.redux' import App from './App' //建立store const store = createStore(reducer); //store.subscribe方法接受的參數是一個函數, // 因此將ReactDOM.render方法寫在一個函數內 function listen() { //將store,action建立函數分別以屬性的方式傳遞給子組件App ReactDOM.render(<App store={store} add={add} subtraction={subtraction}/>, document.querySelector('#root')); } //由於剛進入頁面沒有dispatch操做改變store, // 因此listen不會執行,咱們須要手動調用一次 listen(); //重點,改變了store,頁面就會從新渲染, // 能夠試試不寫這行代碼會是怎樣的效果 store.subscribe(listen);
App.js
import React from 'react' export default class App extends React.Component { render() { //從屬性中獲取store,action建立函數 const {store, add, subtraction} = this.props; //獲取state let state = store.getState(); return <div> <h1>我有{state.money}元</h1> {/*經過store.dispatch方法改變store,從而頁面也會改變*/} <button onClick={() => {store.dispatch(add())}}> 撿了一塊錢 </button> <button onClick={() => {store.dispatch(subtraction())}}> 掉了一塊錢 </button> </div> } }
index.redux.js
//定義常量方便維護 const ADD = '+', SUBTRACTION = '-'; //給初始狀態一個默認值:{money: 0} export function reducer(state = {money: 0}, action) { //返回一個新的state可使用es6提供的Object.assign()方法,或擴展運算符(此方法須要babel-preset-state-3支持) switch (action.type) { case ADD: return Object.assign({}, state, {money: state.money + 1}); case SUBTRACTION: return {...state, ...{money: state.money - 1}}; default: return state; } } //action建立函數,返回了一個action export function add() { return {type: ADD} } export function subtraction() { return {type: SUBTRACTION} }
這樣咱們就將redux和react結合了起來可是這樣咱們可能會以爲麻煩,由於咱們要將store和action建立函數傳給子組件,當咱們的action比較多時,子組件比較多時,就須要將store和大量的action建立函數一層層的屢次傳遞下去。這樣就會十分麻煩,所以咱們就可使用react-redux
這個庫來幫助咱們實現這個麻煩的過程
react-redux
給咱們提供了一個Provider
組件,咱們能夠把這個組件寫在最外層,這樣被Provider
包裹的全部組件均可以經過props
來獲取state
,不管組個組件藏得多麼深。
而Provider
組件只接受一個屬性,那就是store
那麼咱們index.js的代碼就變成下面這樣了:
import React from 'react' import ReactDOM from 'react-dom' import {createStore} from 'redux' import {Provider} from 'react-redux' import {reducer} from './index.redux' import App from './App' //建立store const store = createStore(reducer); ReactDOM.render( <Provider store={store}> <App/> </Provider>, document.querySelector('#root'));
固然,只有Provider
組件是不夠的,咱們還須要connect
來幫助咱們獲取state和action,沒錯,connect
就是幫助咱們獲取state和action的
那麼問題就來了,咱們的組件可不是須要項目中全部的state和action,只須要其中的一部分就能夠了,因此connect
會接受兩個參數,第一個參數它能夠幫咱們篩選state,第二個參數能夠幫咱們篩選action。
咱們能夠把這兩個參數寫成函數的形式,
參數1,
function mapStateToProps(state) { return { money: state.money } }
參數2,
function actionCreators() { return { subtraction, add } }
咱們能夠發現這兩個函數都是返回了一個對象,第一個函數返回了咱們須要的state,第二個函數返回了咱們須要的action建立函數
那麼app.js 的代碼就變成這樣了:
import React from 'react' import {connect} from 'react-redux' import {add, subtraction} from './index.redux' class App extends React.Component { render() { //由於connect的緣由,state和action咱們已經能夠從屬性中獲取了 const {money, add, subtraction} = this.props; return <div> <h1>我有{money}元</h1> {/*這個時候不須要咱們dispatch了*/} <button onClick={add}> 撿了一塊錢 </button> <button onClick={subtraction}> 掉了一塊錢 </button> </div> } } //connect所須要的參數 //函數返回的咱們須要的狀態,咱們須要money,就從state中取出money //假如咱們還須要house,就增長一個house:state.house function mapStateToProps(state) { return { money: state.money } } //connect須要的第二參數 //返回咱們須要的action建立函數 function actionCreators() { return { subtraction, add } } //上面兩個函數返回的都是對象 //經過connect將state和action建立函數當作屬性傳遞給組件 export default App = connect(mapStateToProps, actionCreators())(App);
若是熟悉es6裝飾器的語法那就更好了,可使咱們的代碼變得更優雅
app.js
import React from 'react' import {connect} from 'react-redux' import {add, subtraction} from './index.redux' @connect( state => ({money: state.money}), { subtraction, add }) export default class App extends React.Component { render() { //由於connect的緣由,state和action咱們已經能夠從屬性中獲取了 const {money, add, subtraction} = this.props; return <div> <h1>我有{money}元</h1> {/*這個時候不須要咱們dispatch了*/} <button onClick={add}> 撿了一塊錢 </button> <button onClick={subtraction}> 掉了一塊錢 </button> </div> } }
看到這裏再回頭看看最開始圖片,就能搞清楚redux的工做流程到底是怎樣的。