前端數(shù)據(jù)流之Redux篇(知識點)

redux中文文檔

適用場景

UI層簡單,沒有什么互動的,不需要redux

使用方式復雜哼蛆,與服務(wù)器大量交互构拳,視圖層要從多個來源獲取數(shù)據(jù)的咆爽,使用redux
比如說:

  • 組件狀態(tài)要功響
  • 一個組件更改全局狀態(tài)
  • 一個組件需要改變另一個組件的狀態(tài)

設(shè)計思想

web應(yīng)用是個狀態(tài)機,視圖與狀態(tài)一一對應(yīng)置森。
所有的狀態(tài)斗埂,保存在一個對象里。

基本使用

npm i -g create-react-app安裝包
create-react-app redux-api創(chuàng)建項目
npm i redux安裝redux

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux'

// 1.定義reducer
// state:容器的初始狀態(tài)
// action:修改state的行為類型
//        type - 行為類型凫海; payload(可選的)其他數(shù)據(jù)
function Reducer(state = 0, action) {
  const {type} = action
  if (type === 'INCREMENT') {
    return state + 1
  } else if (type === 'DECREMENT') {
    return state - 1
  } else {
    return state
  }
}
// 2.創(chuàng)建store
const store = createStore(Reducer, 110)
// 3.獲取狀態(tài)
console.log(store.getState())
// 4.更新狀態(tài)
// store.dispatch({type:行 為類型呛凶,額外參數(shù)})
// 發(fā)起dispatch就是在調(diào)用reducer,dispatch接收的參數(shù)叫action
setTimeout(() => {
  store.dispatch({
    type: 'INCREMENT'
  })
}, 1000);
// 5.檢測狀態(tài)變化行贪,驅(qū)動視圖更新
store.subscribe(() => {
  console.log(store.getState())
  render()
})

const Counter = (props) => {
  return <div>
    <h1>{props.value}</h1>
    <button>Increment</button>
    <button>Decrement</button>
  </div>
}

function render() {
  ReactDOM.render(<Counter value={store.getState()}></Counter>, document.getElementById('root'));
}

render()

核心概念

  • store
    保存數(shù)據(jù)的容器漾稀,通過createStore來生成模闲。
import { createStore } from 'redux'
const store = createStore(Reducer, 110)
  • state
    包含所有的數(shù)據(jù)。
    一個state對應(yīng)一個view崭捍。
const state = store.getState()
  • action
    view發(fā)出action尸折,通知state進行改變。
    action是一個對象殷蛇,type屬性必須有实夹,表示名稱。其他屬性自由設(shè)置粒梦。
    action是改變state的唯一方法亮航。
const action = {
  type: 'INCREMENT',
  payload: 'learn redux'
}
  • action creator
    生成action的函數(shù)
const ADD_TODO = '添加todo'
function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}
const action = addTodo('learn redux')
  • store.dispatch
    view通過此方法發(fā)出action。也是唯一的方法匀们。
    store.dispatch接受action對象作為參數(shù)塞赂,然后將其發(fā)送出去。
  store.dispatch({
    type: 'INCREMENT',
    payload: 'learn redux'
  })
store.dispatch(addTodo('learn redux'))
  • reducer
    state的計算過程昼蛀。接受action和當前state作為參數(shù)宴猾,返回新的state
function Reducer(state = 0, action) {
  const {type} = action
  if (type === 'INCREMENT') {
    return state + 1
  } else if (type === 'DECREMENT') {
    return state - 1
  } else {
    return state
  }
}

一般情況下叼旋,store.dispatch方法會觸發(fā)reducer的自動執(zhí)行仇哆,所以store生成時,需要傳入reducer

const store = createStore(Reducer, 110)

reducer是純函數(shù)夫植,同樣的輸入讹剔,必定得到同樣的輸出。
reducer函數(shù)里的state不能改變详民,必須返回一個全新的對象延欠。最好把state設(shè)置為只讀。

// state是對象
function reducer(state, action) {
  return { ...state, ...newState}
}
// state是數(shù)組
function reducer(state, action) {
  return [ ...state, newItem]
}
  • store.subscribe()
    監(jiān)聽方法沈跨,state改變由捎,就會立即執(zhí)行。
    view更新的函數(shù)放入其中饿凛,就會實現(xiàn)view的自動渲染狞玛。
store.subscribe(() => {
  console.log(store.getState())
  render()
})

如果store.subscribe方法返回函數(shù),調(diào)用這個函數(shù)涧窒,即可解除監(jiān)聽

let unsubscribe = store.subscribe(() => {
  connsole.log(store.getState())
})
unsubscribe() // 解除監(jiān)聽

reducer的拆分

項目中state一般很龐大心肪,所以需要對reducer進行拆分

拆分前的代碼:

const Reducer = (state = {}, action = {}) => {
  const { type, payload } = action
  switch (type) {
    case 'ADD_CHAT':
      return Object.assign({}, state, {
        charLog: state.charLog.concat(payload)
      })
    case 'CHANGE_STATUS':
      return Object.assign({}, state, {
        statusMessage: payload
      })
    case 'CHANGE_USERNAME':
      return Object.assign({}, state, {
        userName: payload
      })
    default: return state
  }
}

拆分后的文件結(jié)構(gòu)


chatLog.js

export default function chatLog (state = [], action) {
  const { type, payload } = action
  switch (type) {
    case 'ADD_CHAT':
      return [...state, payload]
    default: return state
  }
}

statusMessage.js

export default function statusMessage (state = '', action) {
  const { type, payload } = action
  switch (type) {
    case 'CHANGE_STATUS':
      return payload
    default: return state
  }
}

userName.js

export default function userName (state = '', action) {
  const { type, payload } = action
  switch (type) {
    case 'CHANGE_USERNAME':
      return payload
    default: return state
  }
}

index.js

import chatLog from './chatLog'
import statusMessage from './statusMessage'
import userNameChange from './userName'
import { combineReducers } from 'redux'

// 寫法1:不推薦
// export default function (state = {}, action = {}) {
//   return {
//     chatLog: chatLog(state.chatLog, action),
//     statusMessage: statusMessage(state.statusMessage, action),
//     userName: userNameChange(state.userName, action)
//   }
// }

// 寫法2:推薦
export default combineReducers({
  chatLog,
  statusMessage,
  userName: userNameChange
})

當需要使用拆分后的reducer時,只需要在srcindex.js

import rootReducer from './reducer'
const store = createStore(rootReducer)

中間件

位于“中間”纠吴,為“兩側(cè)”提供服務(wù)的函數(shù)硬鞍。

redux-logger

npm i redux-logger安裝包
index.js中修改下面幾處:

import { applyMiddleware, createStore } from 'redux'
import { createLogger } from 'redux-logger'

const logger = createLogger()

const store = createStore(
  reducer,
  applyMiddleware(logger)
)

再次使用時,可以看到


redux-thunk

用來搭建異步action構(gòu)造器

npm i redux-thunk安裝包

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { applyMiddleware, createStore } from 'redux'
import { createLogger } from 'redux-logger'
import thunk from 'redux-thunk'
import reducer from './reducer'

const logger = createLogger()

const store = createStore(
  reducer,
  applyMiddleware(thunk, logger)
)

function increment () {
  return {type: 'INCREMENT'}
}

function asyncIncrement () {
  return function (dispatch, getState) {
    setTimeout(() => {
      dispatch(increment())
    }, 1000);
  }
}

store.subscribe(() => {
  render()
})

const Counter = (props) => {
  return <div>
    <h1>{props.value}</h1>
    <button onClick={props.increment}>Increment</button>
    <button onClick={props.asyncIncrement}>AsyncIncrement</button>
  </div>
}

function render() {
  ReactDOM.render(
  <Counter 
    value={store.getState().increment}
    increment={() => store.dispatch(increment())}
    asyncIncrement={() => store.dispatch(asyncIncrement())}></Counter
  >, 
  document.getElementById('root'));
}

render()

react和redux的連接

  • 基本概念
    react-redux中組件分兩類:ui組件和容器組件
    ui組件:只負責ui呈現(xiàn),沒邏輯
    容器組件:管理數(shù)據(jù)和業(yè)務(wù)邏輯固该,有內(nèi)部狀態(tài)碑隆,使用redux的api
    connect:用于從ui組件生成容器組件。

  • 小案例

目標:實現(xiàn)ui組件和容器組件的分離蹬音,實現(xiàn)點擊+1和點擊-1的功能

create-react-app react-redux-demo 創(chuàng)建新項目
npm i redux react-redux 安裝包

文件結(jié)構(gòu)


CouterContainer.js容器組件

import {connect} from 'react-redux'
import Counter from '../components/Counter'

const mapStateToProps = state => {
  return {
    value: state
  }
}

const mapDispatchToProps = dispatch => {
  return {
    handleIncrement () {
      dispatch({
        type: 'INCREMENT'
      })
    },
    handleDecrement () {
      dispatch({
        type: 'DECREMENT'
      })
    }
  }
}

const CounterContainer = connect (
  mapStateToProps,
  mapDispatchToProps
) (Counter)

export default CounterContainer

Counter.jsui組件

import React from 'react'

const Counter = (props) => {
  return (
    <div>
      <h1>Counter Component</h1>
      <h1>{props.value}</h1>
      <button onClick={props.handleIncrement}>點擊+1</button>
      <button onClick={props.handleDecrement}>點擊-1</button>
    </div>
  )
}

export default Counter

store > index.js

import {createStore} from 'redux'

function reducer (state = 100, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}

const store = createStore(reducer)

export default store

index.js引入Providerstore

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from 'react-redux'
import store from './store'

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>
  , document.getElementById('root'));

app.js 引入容器組件

import React from 'react';
import CounterContainer from './containers/CouterContainer'

function App() {
  return (
    <div className="App">
      <CounterContainer />
    </div>
  );
}

export default App;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末上煤,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子著淆,更是在濱河造成了極大的恐慌劫狠,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件永部,死亡現(xiàn)場離奇詭異独泞,居然都是意外死亡,警方通過查閱死者的電腦和手機苔埋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門懦砂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人组橄,你說我怎么就攤上這事荞膘。” “怎么了玉工?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵羽资,是天一觀的道長。 經(jīng)常有香客問我遵班,道長屠升,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任狭郑,我火速辦了婚禮腹暖,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘翰萨。我一直安慰自己脏答,他們只是感情好,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布缨历。 她就那樣靜靜地躺著以蕴,像睡著了一般糙麦。 火紅的嫁衣襯著肌膚如雪辛孵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天赡磅,我揣著相機與錄音魄缚,去河邊找鬼。 笑死,一個胖子當著我的面吹牛冶匹,可吹牛的內(nèi)容都是我干的习劫。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼嚼隘,長吁一口氣:“原來是場噩夢啊……” “哼诽里!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起飞蛹,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤谤狡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后卧檐,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體墓懂,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年霉囚,在試婚紗的時候發(fā)現(xiàn)自己被綠了捕仔。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡盈罐,死狀恐怖榜跌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情盅粪,我是刑警寧澤斜做,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站湾揽,受9級特大地震影響瓤逼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜库物,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一霸旗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧戚揭,春花似錦诱告、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至潜必,卻和暖如春靴姿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背磁滚。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工佛吓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留宵晚,地道東北人。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓维雇,卻偏偏與公主長得像淤刃,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子吱型,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359