React - Redux - 教學 - Todos 範例解析

React - Redux - 教學

我為了教別人Redux,反而自己因此受惠學會一點點Redux的概念

原本,對方給我原文版的

https://redux.js.org/basics/example/

後來我找到中文版的解說

https://chentsulin.github.io/redux/index.html

可以參看線上的的程式碼

https://codesandbox.io/s/github/reduxjs/redux/tree/master/examples/todos

可以從src底下的index.js做為進入點開始看

可能要學Redux要有一個叫State的概念。

會先建立一個Redux如

import { createStore } from 'redux'

const store = createStore(rootReducer)

這個store ,就會有自己的state

而如果要改變這個store的state,必須使用store的方法

store.dispatch

但因為範例已經將store設定到 Provider

<Provider store={store}>

所以該tag底下的所有元件,都可直接dispatch,而不必加上「store.」

dispatch會放入更動的動作(action)與資料

如使用

store.dispatch({ type: 'INCREMENT' })

或在component裡面則是如下

dispatch(addTodo(input.value))

以上這個動作會變成如下

dispatch({type: 'ADD_TODO',id: nextTodoId++,text})

這個會觸發所有store底下的所有state屬性

store所有的state屬性,其實就在一開始包版時就放入的資料

const store = createStore(rootReducer)

rootReducer則會去看reducers/index.js

裡面會有如下

export default combineReducers({todos,visibilityFilter})

這其實也有點像

export default combineReducers(state)的感覺(我蝦猜的)

所以當dispatch後,就會觸發todos及visibilityFilter兩個的state

todos的會如下

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

而visibilityFilter 會如下

import { VisibilityFilters } from '../actions'

const visibilityFilter = (state = VisibilityFilters.SHOW_ALL, action) => {
  switch (action.type) {
    case 'SET_VISIBILITY_FILTER':
      return action.filter
    default:
      return state
  }
}

export default visibilityFilter

為action就是dispatch裡面放的東西 dispatch(action)

所以會用action.type來判斷,是不是跟自己有關,跟自己有關,就會改更自己的state(todos或visibilityFiltre)

在state變更後,有關的component或containers也會隨著應變

在Components下的程式

會有如

const Link = ({ active, children, onClick }) => (
    <button
       onClick={onClick}
       disabled={active}
       style={{
           marginLeft: '4px',
       }}
    >
      {children}
    </button>
)

其實就是

const Link = (我的屬性成員) => (我的畫面)

另外在用

Link.propTypes = {
  active: PropTypes.bool.isRequired,
  children: PropTypes.node.isRequired,
  onClick: PropTypes.func.isRequired
}

說明屬性成員,的資料型態,及是否必填

再來是

export default Link

讓外部程式看得見

在containers有VisibleTodoList.js

如下

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

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

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

感覺就是,我是跟VisibleTodoList是跟TodoList有關,就是一種類似繼承的概念,

並透過connect,將屬性與他的物件屬性連結在一起。

這像是動態注入還是相依性注入的概念,也就是TodoList不先寫死他的預設值及處理方式,而是由他的繼承類別,去幫他實作這部分。

就像TodoList是介面,而VisibleTodoList是實作後的類別

在Link.js有

const Link = ({ active, children, onClick })

children,應該也一個關鍵字,也就是他的元件之間的資料或dom

    <FilterLink filter={VisibilityFilters.SHOW_ACTIVE}>
      Active
    </FilterLink>

Active就是children

而在 FilterLink.js裡有

const mapStateToProps = (state, ownProps) => ({
  active: ownProps.filter === state.visibilityFilter
})

const mapDispatchToProps = (dispatch, ownProps) => ({
  onClick: () => dispatch(setVisibilityFilter(ownProps.filter))
})

看 有ownProps及 ownProps

大概也是關鍵字,是屬於自己這個tag的專有屬性。

大概到這裡,很多稿不懂得邏輯,應該就搞懂了。