Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

redux 簡介 #3

Open
aszx87410 opened this issue Dec 3, 2018 · 0 comments
Open

redux 簡介 #3

aszx87410 opened this issue Dec 3, 2018 · 0 comments
Labels
Front-End Front-End React Reat

Comments

@aszx87410
Copy link
Owner

最近這陣子最夯的前端技術應該就是redux
趁著比較有空的時候,看了一堆文件跟教學,算是對redux有了一點小小心得
在這邊幫自己做個筆記,也試著用自己的話解釋一下這個東西
話說其實官方文件我覺得寫得不錯,而且中文翻譯版也不錯,很多東西都講得深入淺出,很推薦去看一下

適合閱讀這篇文章的人:

  1. 對redux有興趣
  2. 知道React在幹嘛
  3. 知道flux基本概念

不適合閱讀這篇文章的人:

  1. 不知道react跟flux在幹嘛(可以先去補完這方面知識再回來看)

既然你已經懂flux了,就直接切入正題吧!

flux裡面有幾個重要的概念:actionaction creatordispatcherstoreview

Action

先從action開始,我們知道在flux的架構裡面,action唯一可以改變store裡面的數據的方法
reduxflux在這方面比較不一樣的點是,
flux是經由Action creator直接建立action並且dispatch出去,像是

var addItem = function(item) {
  Dispatcher.dispatch({
    type: ActionTypes.ADD_ITEM,
    item: item
  });
}
ActionCreator.addItem(..);

但是在redux裡面,ActionCreator只會產生一個純粹的javascript object
你要自己dispatch出去

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  };
}
store.dispatch(addTodo(text));

而且跟flux的差別是,dispatch這個動作是用store來達成
把原本的Dispatcher這個東西拿掉了

再來,redux裡面只有一個store
這一點也跟flux滿不一樣的,那這時候可能會有疑問說:只有一個store,把所有資料都放在這邊,不會很雜亂嗎?
接著就要引入一個redux裡面很重要的概念:reduce

reduce

什麼是reduce?其實很簡單,就是(previousState, action) => newState
給你現在的狀態跟要執行的動作,傳回一個新的狀態
例如說我們現在要新增一個todo的item

function addTodos(state, action) {
	return [...state, {
    text: action.text,
    completed: false
  }];
}

之前的狀態->新增一筆資料->新的狀態
這就像是一個狀態機,只要保證input跟action一樣,就能保證輸出的結果永遠都一樣
例如f(1)=1的話,永遠不會出現f(1)=2的情形
給一個狀態跟操作->改變資料->傳回新的狀態

而在這邊值得注意點的是,為什麼不寫成

function addTodos(state, action) {
	return state.push({
  	text: action.text,
    completed: false
  })
}

Immutable State

這是之前React有提過的概念:Immutable State
就是說store裡面的數據,是不可改變的,你不能直接對它做編輯
你能做的就只有把整個state都換掉
為什麼要這樣做?
這跟React有一點關係,還記得一個很重要的概念嗎?always re-render
但其實如果數據沒有改變的話,根本就不需要重新render
React裡面有一個function是shouldComponentUpdate
你可以直接寫成:

shouldComponentUpdate: function(nextProps, nextState) {
  return this.props !== nextProps;
}

如果上個狀態跟現在的狀態不相等的話,才需要update

先假設我們的state現在是可以編輯的,來看看會發生什麼事

var prevState = {name:"yo"}
var newState = prevState;
newState.name = "new"
prevState===newState //true

在這個情形底下,因為===只會比較這兩個變數所指向的記憶體位置是否一樣,所以回傳了true
所以每當我們要改變state的時候,就要重新new一份

var prevState = {name:"yo"}
var newState = {name:"new"};
prevState===newState //false

以上是只有一筆資料的簡單情形,如果資料很多怎麼辦?
每次都重新new一份不是很浪費空間嗎?
所以可以採用一種更好的做法,對於沒有變動的地方,就直接使用;有變動的地方再new一份
應該會長得有點像這樣

var prevState = {
	todos: [1,2,3],
	name: "peter"
}

var newState = {
	todos: prevState.todos,
	name: "new"
}

prevState.todos===newState.todos//true
prevState==newState //false

那我們在實做的時候,有幾種方法可以選擇

  1. React.addons.update
  2. Immutable.js
  3. ES7的object spread

但其實不可改變的state這個概念只是推薦用法,你不這樣用,想要改變它也是可以
只是可能有些很酷的功能沒有辦法使用而已

再回到reducer

還記得store裡面儲存了整個app的state,然後會切割不同部分,交給不同的reducer去處理,最後再統一結果
所以結構上面還是相當良好的
大家可以直接看官方文檔裡面的這段code

function todos(state = [], action) {
  switch (action.type) {
  case ADD_TODO:
    return [...state, {
      text: action.text,
      completed: false
    }];
  case COMPLETE_TODO:
    return [
      ...state.slice(0, action.index),
      Object.assign({}, state[action.index], {
        completed: true
      }),
      ...state.slice(action.index + 1)
    ];
  default:
    return state;
  }
}

function visibilityFilter(state = SHOW_ALL, action) {
  switch (action.type) {
  case SET_VISIBILITY_FILTER:
    return action.filter;
  default:
    return state;
  }
}

function todoApp(state = {}, action) {
  return {
    visibilityFilter: visibilityFilter(state.visibilityFilter, action),
    todos: todos(state.todos, action)
  };
}

todoApp就是整個store,然後切割成visibilityFiltertodos兩個reducer
分別對自己所關心的state做出反應,回傳新的state
這份code用了大量的ES6,所以看redux的好處就是還可以順便學習ES6

其實redux的一些特性到這邊講得差不多了
就跟flux滿像的,就action creator -> 發action -> store接到action -> 分配給reducer -> 產生新的state
view那邊就subscribe state的變化,這點跟之前flux一樣
接著要講一個redux比較不一樣的點

Middleware

有接觸過express的人相信對middleware不會太陌生,在redux裡面的middleware指的是
從action到store的這段路程當中,可以經過很多middleware
action -> middleware1 -> middleware2 -> store
這樣的好處是什麼呢?

  1. 可以很輕鬆地log
  2. 非同步API!!

redux在middleware裡面非同步API的實作我沒有看得很懂,不過大概可以講一下概念
原本的action是指一個Plain Javascript Object,就是長得像這個樣子

{
  type: "ADD_TODO"
}

那現在如果action變成一個function呢?
就可以寫一個專門處理function的middleware
只要碰到是function,就立刻執行
那假如今天這個function是一個去遠端拿資料的操作

//action creator
function receiveResult(result){
  return {
    type: "RECEIVE_RESULT",
    result: result
  }
}

function get(){
  return function(){
    API.get(function(result){
      store.dispatch(receiveResult(result));
    }
  }
}
store.dispatch(getSomething());

middleware在處理到的時候,就會執行這個function,而function執行完會dispatch一個action
詳細的說明可以看我最下面附的參考資料,或是直接參考官方文件
因為官方文件真的寫得很不錯

原本在flux的架構裡面,沒有很明確指定說怎麼做非同步的api呼叫
但是在redux給了我們明確解答: 就是發action

與React的結合

最後稍微提一下怎麼跟React做結合
redux提供了Provider,讓你把store注入到你的react元件裡面去
所以在root的地方必須這樣做

let store = createStore(todoApp);

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

在個別元件的地方,用connect這個指定這個元件要接收哪些state
如果沒有指定的話,就會接收到整個store的state,也就是這整個應用程式的state

在看redux的時候,只要有flux的基礎,就會發現兩者有滿多地方類似
redux簡化了一些步驟,引進一些flux原本沒有的東西,但是核心思想還是差不多的,就是「單向資料流」
redux的sample code也很棒,分成幾個範例,一個叫做real world的範例最完整
裡面應用了react-routergithub api,實際示範如何與這些東西做搭配
只要能熟悉這個範例,相信採用redux來開發SPA會還滿得心應手的

最後,再次推薦官方文件
下面列出我在寫這篇文之前看過的一些文章,有興趣的可以看看

ref:
Redux 初步尝试
还在纠结 Flux 或 Relay,或许 Redux 更适合你
Redux basic tutorial
Redux 中文文档
redux-promise
redux-tutorial
Redux 核心概念
Thunk 函数的含义和用法
Functional JavaScript Mini Book
Redux 入門
The React.js Way: Flux Architecture with Immutable.js
react - Advanced Performance
gaearon/normalizr

@aszx87410 aszx87410 added React Reat Front-End Front-End labels Dec 3, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Front-End Front-End React Reat
Projects
None yet
Development

No branches or pull requests

1 participant