react進階-redux

個人博客地址

react進階-redux

開始

Redux,一種新型的前端“架構模式”。經常和 React.js 一併提出,你要用 React.js 基本都要伴隨着 ReduxReact.js 結合的庫 React-redux

要注意的是,ReduxReact-redux 並不是同一個東西。Redux是一種架構模式(Flux 架構的一種變種),它不關注你到底用什麼庫,你可以把它應用到 ReactVue,甚至跟 jQuery結合都沒有問題。而 React-redux 就是把 Redux這種架構模式和 React.js 結合起來的一個庫,就是 Redux架構在 React.js 中的體現。

安裝reduxreact-redux

npm i -S redux react-redux

那麼如何去使用它們? 首先得了解provider組件,和connect以及store,action,reducer.

解析 redux

redux中含有store,action,reducer.redux的作用是什麼? 它們是如何工作的.

redux的作用就是用來管理數據狀態的.

隨着 JavaScript 單頁應用開發日趨複雜,JavaScript 需要管理比任何時候都要多的 state (狀態)。 這些 state 可能包括服務器響應、緩存數據、本地生成尚未持久化到服務器的數據,也包括 UI 狀態,如激活的路由,被選中的標籤,是否顯示加載動效或者分頁器等等。

管理不斷變化的 state 非常困難。如果一個 model 的變化會引起另一個 model 變化,那麼當 view 變化時,就可能引起對應 model 以及另一個 model 的變化,依次地,可能會引起另一個 view 的變化。直至你搞不清楚到底發生了什麼。state 在什麼時候,由於什麼原因,如何變化已然不受控制。 當系統變得錯綜複雜的時候,想重現問題或者添加新功能就會變得舉步維艱。

store,action,reducer是如何協調工作的?

假設我們有一個事情清單要維護:

const state = {
  todos: [
    {
      text: 'eat food',
      completed: true,
    },
    {
      text: 'Exercise',
      completed: false
    },
  ]
};

我們如何去維護這些數據? 比如增加一個要做的事情.

reducer就是做這個工作的,它完成了數據的初始化,並且決定了對這個數據有哪些行爲.並且它還是一個純函數(即不對參數進行修改的函數),要保證只要輸入的參數一樣,那麼結果一定相同.

const defaultState = {
  todos: [],
}
function todoReducer(state=defaultState, action) {
  switch (action.type) {
    case 'insert':
      const todo = { text: action.text, completed: false };
      return { ...state, todos: [...state.todos, todo] };
    default:
      return { ...state };
  }
}

我們只能夠通過這個todoReducer來操作數據,以上我們需要傳入行爲action,定義操作的類型以及數據.顯然這樣通過字符串的形式來判斷行爲,是不行的.所以我們可以預先定義action,然後再傳入,可以聯想到設計模式中的工廠模式.

action.js:

export const actionType = {
  insert: 'insert',
}

export const insertTodo = (value)=>({
  type: actionType.insert,
  text: value,
});

acitonType規定了aciton的有那些行爲,並且提供了一些action,這裏我們演示只有一個增加的action.

然後我們reducer就能夠寫成:

import { actionType } from './action';

const defaultState = {
  todos: [],
}
function todoReducer(state = defaultState, action) {
  switch (action.type) {
    case actionType.insert:
      const todo = { text: action.text, completed: false };
      return { ...state, todos: [...state.todos, todo] };
    default:
      return { ...state };
  }
}
export default todoReducer;

這樣子的話,我們可以通過獲取action,給reducer來完成我們對數據的操作.

那麼我們如何獲取state數據? 如果有一個getter函數就好了,這就是store,並且它還封裝了reducer.

store.js

import todoReducer from './todoReducer';

function createStore(reducer) {
  let state = undefined;
  const dispatch = (action) => {
    state = reducer(state, action);
  }
  // 初始化state
  dispatch({});
  const getState = () => state;
  return { getState, dispatch };
}

const store = createStore(todoReducer);
export default store;

這裏的store有獲取state的方法getState,還有對其操作的函數dispatch.故我們只要通過store就能夠對數據進行操作.

示例:

import store from './store';
import { insertTodo } from './action';
import { Component } from 'react';

class Test extends Component {
  constructor(props) {
    super(props);
    console.table(store.getState());
    store.dispatch(insertTodo('run'));
    store.dispatch(insertTodo('write a blog'));
    console.table(store.getState());
  }
  render() { 
    return (null);
  }
}
 
export default Test;


這裏我們增加了兩個事件,驗證一下:
在這裏插入圖片描述

這就是redux的工作原理,還有一個問題就是store就像是全局變量,可能兩個組件的數據變量可能會重複,會出現不方便維護,解決方案是我們爲每個組件設置成單獨的數據域,就好像:

{
    app:{...},
    todoList: {...},
    ...
}

類似這種效果,這樣也不會產生數據的污染.

redux恰好就提供了combineReducers函數來實現這個效果.它就是結合reducer來爲索引.

先創建一個總的 reducer:

import { combineReducers } from 'redux';
import todoReducer from './todoReducer';

const rootReducer = combineReducers({
  todoReducer,
});

export default rootReducer;

這裏將剛纔的todoReducer放進去總reducer.

然後我們的store引入該reducer:

import rootReducer from './rootReducer';
import { createStore } from 'redux';

const store = createStore(rootReducer);
export default store;

createStore,是redux已經幫我們寫好了,我們直接用就行.

  constructor(props) {
    super(props);
    console.table(store.getState());
    store.dispatch(insertTodo('run'));
    store.dispatch(insertTodo('write a blog'));
    console.table(store.getState());
  }

我們同樣的對其進行打印,看看數據是否有變化.
在這裏插入圖片描述

我們可以看到,store保存的數據結構已經發生了變化,已經開始分區域保存數據,實際保存數據的其實是reducer,只不過store包含了所有的reducer.

結合 react-redux

React-ReduxRedux的官方React綁定庫。它能夠使你的React組件從Redux store中讀取數據,並且向store分發actions以更新數據.

react-redux提供了兩個東西,<Provider/>組件和connect函數.

React-Redux 將所有組件分成兩大類:UI 組件(presentational component)和容器組件(container component)。

UI 組件:

只負責 UI 的呈現,不帶有任何業務邏輯,沒有狀態(即不使用this.state這個變量,所有數據都由參數(this.props)提供,

不使用任何 Redux 的 API.

容器組件:

容器組件的特徵恰恰相反,負責管理數據和業務邏輯,不負責 UI 的呈現,帶有內部狀態,使用 Redux 的 API

Provider

React-Redux 提供<Provider/>組件,能夠使你的整個app訪問到Redux store中的數據.

源碼:

class Provider extends Component {
  getChildContext() {
    return {
      store: this.props.store
    };
  }
  render() {
    return this.props.children;
  }
}

可以看到其作用就是將store綁定在了上下文對象context,然後子組件就可以通過context獲取到store.

connect()

connect方法,用於從 UI 組件生成容器組件.

import React, { Component } from 'react'
import PropTypes from 'prop-types'

export const connect = (mapStateToProps) => (WrappedComponent) => {
  class Connect extends Component {
    static contextTypes = {
      store: PropTypes.object
    }

    render () {
      const { store } = this.context
      let stateProps = mapStateToProps(store.getState())
      // {...stateProps} 意思是把這個對象裏面的屬性全部通過 `props` 方式傳遞進去
      return <WrappedComponent {...stateProps} />
    }
  }

  return Connect
}

它從context獲取了store,並且將store裏的數據,通過props注入到了組件,所以組件可以通過props裏讀取store裏的數據,而mapStateToProps,顧名思義就是將store裏的state映射到組件的props.

還能繼續深入對dispatch進行映射,同樣映射到props裏,並且數據dispatch之後要重新渲染組件.

export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {
  class Connect extends Component {
    static contextTypes = {
      store: PropTypes.object
    }

    constructor () {
      super()
      this.state = {
        allProps: {}
      }
    }

    componentWillMount () {
      const { store } = this.context
      this._updateProps()
      // 訂閱,只要dispatch就會調用傳入的函數
      store.subscribe(() => this._updateProps())
    }

    _updateProps () {
      const { store } = this.context
      let stateProps = mapStateToProps
        ? mapStateToProps(store.getState(), this.props)
        : {} // 防止 mapStateToProps 沒有傳入
      let dispatchProps = mapDispatchToProps
        ? mapDispatchToProps(store.dispatch, this.props)
        : {} // 防止 mapDispatchToProps 沒有傳入
      this.setState({
        allProps: {
          ...stateProps,
          ...dispatchProps,
          ...this.props
        }
      })
    }

    render () {
      return <WrappedComponent {...this.state.allProps} />
    }
  }
  return Connect
}

這樣就也就完成了statedispatch映射到props的過程.

實際上react-redux已經封裝了該函數,所以我們直接用就行了.

示例: 將 reduxreact結合起來,圖像化.

import React from 'react';
import TodoList from './components/react-redux/TodoList';
import { connect } from 'react-redux';
import { insertTodo } from './store/action'

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { value: '' };
  }
  handleChange = (e) => {
    const value = e.target.value;
    this.setState({
      value,
    });
  }
  handleClick = () => {
    this.props.insertTodo(this.state.value);
    this.setState({
      value:'',
    });
  }
  render() {
    return (
      <div>
        <input value={this.state.value} onChange={this.handleChange} />
        <button onClick={this.handleClick}>add</button>
        <TodoList todos={this.props.todos} />
      </div>
    );
  }
}
const mapToStateToProps = (state) => {
  return {
    todos: state.todoReducer.todos,
  }
}
const mapToDispatchToProps = (dispatch) => {
  return {
    insertTodo: (value) => { dispatch(insertTodo(value)) },
  }
}
export default connect(mapToStateToProps, mapToDispatchToProps)(App);

這裏我們返回的是一個由connect生成的容器組件.並且完成了映射,將state.todoReducer.todos映射到了props.todos上,dispatch也是如此.所以在組件裏,我們直接用props調用就行了,TodoList就是簡單的react組件.

附上TodoList.js:

import React, { Component } from 'react';
import Todo from './Todo';

class TodoList extends Component {
  render() {
    const { todos } = this.props;
    return (
      <ul>
        {todos.map(todo => {
          return <Todo todo={todo} />;
        })}
      </ul>
    );
  }
}

export default TodoList;

以及Todo.js:

import React, { Component } from 'react';

class Todo extends Component {
  render() {
    const { todo } = this.props;
    return (
      <div>
        {todo.text}
        {todo.completed ? <input type='checkbox' checked />
          : <input type='checkbox' />}
      </div>
    );
  }
}

export default Todo;

記住一定要connect生成的組件一定要在<Provider/>下纔能有效,因爲connect要通過context獲取store,而store放在context的過程是有<Provider/>,前面已經講過了.

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

最後的效果:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章