Redux 計(jì)算衍生數(shù)據(jù)

2021-09-16 10:15 更新

計(jì)算衍生數(shù)據(jù)

Reselect 是用來創(chuàng)建可記憶的(Memoized)、可組合的 selector 函數(shù)。Reselect selectors 可以用來高效地計(jì)算 Redux store 里的衍生數(shù)據(jù)。

可記憶的 Selectors 初衷

首先訪問 Todos 列表示例:

containers/App.js

import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { addTodo, completeTodo, setVisibilityFilter, VisibilityFilters } from '../actions';
import AddTodo from '../components/AddTodo';
import TodoList from '../components/TodoList';
import Footer from '../components/Footer';

class App extends Component {
  render() {
    // 通過 connect() 注入:
    const { dispatch, visibleTodos, visibilityFilter } = this.props;
    return (
      <div>
        <AddTodo
          onAddClick={text =>
            dispatch(addTodo(text))
          } />
        <TodoList
          todos={this.props.visibleTodos}
          onTodoClick={index =>
            dispatch(completeTodo(index))
          } />
        <Footer
          filter={visibilityFilter}
          onFilterChange={nextFilter =>
            dispatch(setVisibilityFilter(nextFilter))
          } />
      </div>
    );
  }
}

App.propTypes = {
  visibleTodos: PropTypes.arrayOf(PropTypes.shape({
    text: PropTypes.string.isRequired,
    completed: PropTypes.bool.isRequired
  })),
  visibilityFilter: PropTypes.oneOf([
    'SHOW_ALL',
    'SHOW_COMPLETED',
    'SHOW_ACTIVE'
  ]).isRequired
};

function selectTodos(todos, filter) {
  switch (filter) {
  case VisibilityFilters.SHOW_ALL:
    return todos;
  case VisibilityFilters.SHOW_COMPLETED:
    return todos.filter(todo => todo.completed);
  case VisibilityFilters.SHOW_ACTIVE:
    return todos.filter(todo => !todo.completed);
  }
}

function select(state) {
  return {
    visibleTodos: selectTodos(state.todos, state.visibilityFilter),
    visibilityFilter: state.visibilityFilter
  };
}

// 把組件包起來,以此來注入 dispatch 和 state
export default connect(select)(App);

上面的示例中,select 調(diào)用了 selectTodos 來計(jì)算 visibleTodos。運(yùn)行沒問題,但有一個(gè)缺點(diǎn):每當(dāng)組件更新時(shí)都會(huì)計(jì)算 visibleTodos。如果 state tree 非常大,或者計(jì)算量非常大,每次更新都重新計(jì)算可能會(huì)帶來性能問題。Reselect 能幫你省去這些沒必要的重新計(jì)算。

創(chuàng)建可記憶的 Selector

我們需要一個(gè)可記憶的 selector 來替代這個(gè) select,只在 state.todos or state.visibilityFilter 變化時(shí)重新計(jì)算 visibleTodos,而在其它部分(非相關(guān))變化時(shí)不做計(jì)算。

Reselect 提供 createSelector 函數(shù)來創(chuàng)建可記憶的 selector。createSelector 接收一個(gè) input-selectors 數(shù)組和一個(gè)轉(zhuǎn)換函數(shù)作為參數(shù)。如果 state tree 的改變會(huì)引起 input-selector 值變化,那么 selector 會(huì)調(diào)用轉(zhuǎn)換函數(shù),傳入 input-selectors 作為參數(shù),并返回結(jié)果。如果 input-selectors 的值的前一次的一樣,它將會(huì)直接返回前一次計(jì)算的數(shù)據(jù),而不會(huì)再調(diào)用一次轉(zhuǎn)換函數(shù)。

讓我們定義一個(gè)可記憶的 selector visibleTodosSelector 來替代 select

selectors/TodoSelectors.js

import { createSelector } from 'reselect';
import { VisibilityFilters } from './actions';

function selectTodos(todos, filter) {
  switch (filter) {
  case VisibilityFilters.SHOW_ALL:
    return todos;
  case VisibilityFilters.SHOW_COMPLETED:
    return todos.filter(todo => todo.completed);
  case VisibilityFilters.SHOW_ACTIVE:
    return todos.filter(todo => !todo.completed);
  }
}

const visibilityFilterSelector = (state) => state.visibilityFilter;
const todosSelector = (state) => state.todos;

export const visibleTodosSelector = createSelector(
  [visibilityFilterSelector, todosSelector],
  (visibilityFilter, todos) => {
    return {
      visibleTodos: selectTodos(todos, visibilityFilter),
      visibilityFilter
    };
  }
);

在上例中,visibilityFilterSelectortodosSelector 是 input-selector。因?yàn)樗麄儾⒉晦D(zhuǎn)換數(shù)據(jù),所以被創(chuàng)建成普通的非記憶的 selector 函數(shù)。但是,visibleTodosSelector 是一個(gè)可記憶的 selector。他接收 visibilityFilterSelectortodosSelector 為 input-selector,還有一個(gè)轉(zhuǎn)換函數(shù)來計(jì)算過濾的 todos 列表。

組合 Selector

可記憶的 selector 自身可以作為其它可記憶的 selector 的 input-selector。下面的 visibleTodosSelector 被當(dāng)作另一個(gè) selector 的 input-selector,來進(jìn)一步通過關(guān)鍵字(keyword)過濾 todos。

const keywordSelector = (state) => state.keyword;

const keywordFilterSelector = createSelector(
  [visibleTodosSelector, keywordSelector],
  (visibleTodos, keyword) => visibleTodos.filter(
    todo => todo.indexOf(keyword) > -1
  )
);

連接 Selector 和 Redux Store

如果你在使用 react-redux,你可以使用 connect 來連接可忘記的 selector 和 Redux store。

containers/App.js

import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { addTodo, completeTodo, setVisibilityFilter } from '../actions';
import AddTodo from '../components/AddTodo';
import TodoList from '../components/TodoList';
import Footer from '../components/Footer';
import { visibleTodosSelector } from '../selectors/todoSelectors.js';

class App extends Component {
  render() {
    // 通過 connect() 注入:
    const { dispatch, visibleTodos, visibilityFilter } = this.props;
    return (
      <div>
        <AddTodo
          onAddClick={text =>
            dispatch(addTodo(text))
          } />
        <TodoList
          todos={this.props.visibleTodos}
          onTodoClick={index =>
            dispatch(completeTodo(index))
          } />
        <Footer
          filter={visibilityFilter}
          onFilterChange={nextFilter =>
            dispatch(setVisibilityFilter(nextFilter))
          } />
      </div>
    );
  }
}

App.propTypes = {
  visibleTodos: PropTypes.arrayOf(PropTypes.shape({
    text: PropTypes.string.isRequired,
    completed: PropTypes.bool.isRequired
  })),
  visibilityFilter: PropTypes.oneOf([
    'SHOW_ALL',
    'SHOW_COMPLETED',
    'SHOW_ACTIVE'
  ]).isRequired
};

// 把 selector 傳遞給連接的組件
export default connect(visibleTodosSelector)(App);
以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)