Reselect 是用來創(chuàng)建可記憶的(Memoized)、可組合的 selector 函數(shù)。Reselect selectors 可以用來高效地計(jì)算 Redux store 里的衍生數(shù)據(jù)。
首先訪問 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ì)算。
我們需要一個(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
};
}
);
在上例中,visibilityFilterSelector
和 todosSelector
是 input-selector。因?yàn)樗麄儾⒉晦D(zhuǎn)換數(shù)據(jù),所以被創(chuàng)建成普通的非記憶的 selector 函數(shù)。但是,visibleTodosSelector
是一個(gè)可記憶的 selector。他接收 visibilityFilterSelector
和 todosSelector
為 input-selector,還有一個(gè)轉(zhuǎn)換函數(shù)來計(jì)算過濾的 todos 列表。
可記憶的 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
)
);
如果你在使用 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);
更多建議: