React 快速上手 - 08 redux 狀態管理 react-redux

會煮咖啡的貓發表於2018-05-25

redux

本系列目錄

點贊是美德 : )


今天我會快速的過一下 redux 知識,然後討論下 適用性,最後為了讓大家能快速學習,簡化了官方的經典 Demo todo ,跟著我做一遍就行。

目標

  • 了結什麼是 redux
  • 什麼情況下適用
  • 建立一個 redux 應用
  • 安裝除錯外掛

閒話

redux 是一個很深的話題,先聊聊標準學習路線,開啟 redux.js.org/introductio…

筆者為了寫這文章,可是安靜的讀了一遍

首先 Introduction 7 篇

redux-introduction

好像懂了什麼,不知道呢

然後 Base 6 篇

redux-Base

其實 Example: Todo List 這才是最有幫助的

又聽了視訊 Getting Started with Redux 視訊

好囉嗦啊

Redux 視訊 Getting Started with Redux

不過這也是學習的正確姿勢,總要看一遍官方文件,無奈官網設計的更像一個學術站點,而不是類庫工具一類的指導說明

好了好了~吐槽完畢,開始正文!

redux 是什麼, 可以不用麼

ducafecat_2018-05-25-11-27-37

這張圖是 Flux 官網的,畫的很好。

Redux 是一種 資料的管理 方式,介面上發起各種操作 Action ,然後 DispatcherStore 更新狀態 State,推送新狀態到檢視 View

好了,概念一句話說完了,來看看什麼情況下用 Redux

鬥魚介面

這個介面,如果用 React 來實現,在底部的 容器元件 要處理的業務有: 使用者登入、彈幕、主播資訊、視訊進度、道具、打賞、IM聊天、等等還有很多 而且隨著產品迭代,功能只會多

按我們之前的元件拆分,結構是合理,但是這個資料管理麻煩了,各種業務資料壓入子元件,各種業務事件返回到主容器元件,可能的程式碼結構如下

// 狀態
this.state = {
  data1:...,
  data2:...,
  data3:...,
  data...n:...
}

// 事件
function handelEvent1 = {...}
function handelEvent2 = {...}
function handelEvent3 = {...}
function handelEvent...n = {...}

// JSX
<主檢視元件>
  <使用者資訊 data1={this.state.data1} handleEven1={...} handleEven2={...} handleEven...n={...} />
  <主播資訊>
    <基礎資料 data...n={...} handleEven...n={...} />
    <頭像 data...n={...} handleEven...n={... />
    <關注 data...n={...} handleEven...n={... />
    <標籤 data...n={...} handleEven...n={... />
    <熱度 data...n={...} handleEven...n={... />
    ...
  </主播資訊>
  <播放器 ...>
    <... />
    ...
  <播放器>
    ...
  <...播放器/>
  ...
</主檢視元件>
複製程式碼

會發現元件套元件,父父子子的,完全沒法維護了,梳理這些關係就很費時間,而且容易錯誤

Redux 就是來解決這個問題的,每個元件只要執行自己的 Action ,不用返回到父容器

  • 比如 Redux 在彈幕業務中就兩步:
    1. 彈幕發出元件 執行發出彈幕動作 Action 內容 { type: 'BARRAGE_SEND', text: '彈幕訊息' }
    2. 彈幕滾動元件 新彈幕資料被更新到彈幕滾動元件

但是也不好濫用,我看到有些簡單的 表單操作,竟然也套了 Redux ,完全沒必要,自己把握吧

動手建立一個 redux 應用

來個經典例子 todo , 原版 Todo , 我這裡是精簡版,那麼我們開始

  • 動圖效果

react-redux-todo

  • 元件結構

todo 元件結構

第一步: 編寫 Action

介面上產生的操作

let nextTodoId = 0

export const addTodo = text => ({
  type: 'ADD_TODO',
  id: nextTodoId++,
  text
})

export const toggleTodo = id => ({
  type: 'TOGGLE_TODO',
  id
})
複製程式碼
  • 事件的格式
    • type 欄位必須有,表示做什麼操作
    • type 值全域性唯一
    • type 大寫定義
    • 其它欄位自由定義

第二步: 編寫 Reducers

事件對應的響應處理,處理完後返回新 state

const todos = (state = [], action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        {
          id: action.id,
          text: action.text,
          completed: false
        }
      ]
    case 'TOGGLE_TODO':
      return state.map(
        todo =>
          todo.id === action.id ? {...todo, completed: !todo.completed} : todo
      )
    default:
      return state
  }
}

export default todos
複製程式碼

第三步: 合併 Reducers

使用 combineReducers 合併所有的處理過程

import { combineReducers } from 'redux'
import todos from './todos'

export default combineReducers({
  todos
})
複製程式碼

假設你有其它業務, 如: 使用者 user , 購物車 cart

import { combineReducers } from 'redux'
import todos from './todos'
import user from './user'
import cart from './cart'

export default combineReducers({
  todos,
  user,
  cart
})
複製程式碼

第四步: 編寫元件 AddTodo

  • 使用 connect 連線元件
  • 使用 dispatch 方法派發事件
import React from 'react'
import { connect } from 'react-redux'
import { addTodo } from '../redux/actions'

const AddTodo = ({ dispatch }) => {
  let input

  return (
    <div>
      <form onSubmit={e => {
        e.preventDefault()
        if (!input.value.trim()) {
          return
        }
        dispatch(addTodo(input.value))
        input.value = ''
      }}>
        <input ref={node => input = node} />
        <button type="submit">
          Add Todo
        </button>
      </form>
    </div>
  )
}

export default connect()(AddTodo)
複製程式碼

dispatch(addTodo(input.value)) 就發動了 ReduxReducers , 這時 state 更新了

第五步: 編寫元件 TodoList

再來個稍微複雜的

import React from 'react'
import {connect} from 'react-redux'
import {toggleTodo} from '../redux/actions'

const Todo = ({onClick, completed, text}) => (
  <li
    onClick={onClick}
    style={{
      textDecoration: completed ? 'line-through' : 'none'
    }}
  >
    {text}
  </li>
)

const TodoList = ({todos, toggleTodo}) => (
  <ul>
    {todos.map(todo => (
      <Todo key={todo.id} {...todo} onClick={() => toggleTodo(todo.id)} />
    ))}
  </ul>
)

const mapStateToProps = state => ({
  todos: state.todos
})

const mapDispatchToProps = dispatch => ({
  toggleTodo: id => dispatch(toggleTodo(id))
})

export default connect(mapStateToProps, mapDispatchToProps)(TodoList)

複製程式碼

還是 connect 方法 , 這裡重點講下

先看看官方定義 connect()

  • 定義
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
複製程式碼
  • 引數
名稱 說明
mapStateToProps store 繫結 state , 做更新的需要傳入
mapDispatchToProps 繫結派發事件 event , 不傳的話預設 dispatch 物件, 就像上面的 AddTodo 元件
mergeProps [mergeProps(stateProps, dispatchProps, ownProps): props] (Function): 合併屬性自定義,自己不傳的話預設 ·Object.assign ,我也是放空預設的
options 一些選項,我沒怎麼在意,知道有就行

[mapStateToProps], [mapDispatchToProps] 這兩個用到的多,大家自己練習下

第六步: 容器元件

import React from 'react'
import AddTodo from './AddTodo'
import TodoList from './TodoList'

const App = () => (
  <div>
    <AddTodo />
    <TodoList />
  </div>
)

export default App
複製程式碼

這裡簡單,並列排放

最後: 適配 App

import React, {Component} from 'react'
import {createStore} from 'redux'
import {Provider} from 'react-redux'
import TodoApp from './todos/components/App'
import todoReducer from './todos/redux/reducers'

const store = createStore(todoReducer)

class BaseRedux extends Component {
  render() {
    return (
      <Provider store={store}>
        <TodoApp />
      </Provider>
    )
  }
}

export default BaseRedux
複製程式碼
  • 標準程式碼格式
    1. createStore(reducers) 建立 store
    2. Provider 介面卡壓入 store 物件, 子節點都受 Redux 控制

除錯工具 Redux DevTools extension

動圖效果

Redux DevTools extension

1. 安裝 chrome 外掛

開啟 redux-devtools

redux-devtools

2. 配置程式碼

const store = createStore(
  todoReducer,
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
複製程式碼

createStore 時加入 window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()

3. 開啟外掛

開啟 chrome 除錯工具,點選皮膚 Redux

chrome-tab-Redux

codepen

https://codepen.io/ducafecat/pen/RyBEeK

程式碼

參考

相關文章