计算衍生数据

最后更新于:2022-04-01 05:00:47

# 计算衍生数据 [Reselect](https://github.com/faassen/reselect.git) 是用来创建可记忆的(Memoized)、可组合的 **selector** 函数。Reselect selectors 可以用来高效地计算 Redux store 里的衍生数据。 ### 可记忆的 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` 调用了 `selectTodos` 来计算 `visibleTodos`。运行没问题,但有一个缺点:每当组件更新时都会计算 `visibleTodos`。如果 state tree 非常大,或者计算量非常大,每次更新都重新计算可能会带来性能问题。Reselect 能帮你省去这些没必要的重新计算。 ### 创建可记忆的 Selector 我们需要一个可记忆的 selector 来替代这个 `select`,只在 `state.todos` or `state.visibilityFilter` 变化时重新计算 `visibleTodos`,而在其它部分(非相关)变化时不做计算。 Reselect 提供 `createSelector` 函数来创建可记忆的 selector。`createSelector` 接收一个 input-selectors 数组和一个转换函数作为参数。如果 state tree 的改变会引起 input-selector 值变化,那么 selector 会调用转换函数,传入 input-selectors 作为参数,并返回结果。如果 input-selectors 的值的前一次的一样,它将会直接返回前一次计算的数据,而不会再调用一次转换函数。 让我们定义一个可记忆的 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 }; } ); ~~~ 在上例中,`visibilityFilterSelector` 和 `todosSelector` 是 input-selector。因为他们并不转换数据,所以被创建成普通的非记忆的 selector 函数。但是,`visibleTodosSelector` 是一个可记忆的 selector。他接收 `visibilityFilterSelector` 和 `todosSelector` 为 input-selector,还有一个转换函数来计算过滤的 todos 列表。 ### 组合 Selector 可记忆的 selector 自身可以作为其它可记忆的 selector 的 input-selector。下面的 `visibleTodosSelector` 被当作另一个 selector 的 input-selector,来进一步通过关键字(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); ~~~
';