加入收藏 | 设为首页 | 会员中心 | 我要投稿 汽车网 (https://www.0577qiche.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 教程 > 正文

Reudx 计算衍生数据分析

发布时间:2023-04-07 08:52:06 所属栏目:教程 来源:
导读:Reselect 库可以创建可记忆的(Memoized)、可组合的 selector 函数。Reselect selectors 可以用来高效地计算 Redux store 里的衍生数据。

可记忆的 Selectors 初衷
首先访问 Todos 列表示例:

containers/Visib
Reselect 库可以创建可记忆的(Memoized)、可组合的 selector 函数。Reselect selectors 可以用来高效地计算 Redux store 里的衍生数据。

可记忆的 Selectors 初衷
首先访问 Todos 列表示例:

containers/VisibletodoList.js
import { connect } from 'react-redux'
import { toggletodo } from '../actions'
import TodoList from '../components/TodoList'
 
const getVisibletodos = (todos, filter) => {
  switch (filter) {
    case 'SHOW_ALL':
      return todos
    case 'SHOW_COMPLETED':
      return todos.filter(t => t.completed)
    case 'SHOW_ACTIVE':
      return todos.filter(t => !t.completed)
  }
}
 
const mapStatetoProps = state => {
  return {
    todos: getVisibletodos(state.todos, state.visibilityFilter)
  }
}
 
const mapdispatchToProps = dispatch => {
  return {
    onTodoClick: id => {
      dispatch(toggletodo(id))
    }
  }
}
 
const VisibletodoList = connect(
  mapStatetoProps,
  mapdispatchToProps
)(TodoList)
 
export default VisibletodoList
上面的示例中,mapStatetoProps 调用了 getVisibletodos 来计算 todos。运行没问题,但有一个缺点:每当组件更新时都会重新计算 todos。如果 state tree 非常大,或者计算量非常大,每次更新都重新计算可能会带来性能问题。Reselect 能帮你省去这些没必要的重新计算。

创建可记忆的 Selector
我们需要一个可记忆的 selector 来替代这个 getVisibletodos,只在 state.todos or state.visibilityFilter 变化时重新计算 todos,而在其它部分(非相关)变化时不做计算。

Reselect 提供 createSelector 函数来创建可记忆的 selector。createSelector 接收一个 input-selectors 数组和一个转换函数作为参数。如果 state tree 的改变会引起 input-selector 值变化,那么 selector 会调用转换函数,传入 input-selectors 作为参数,并返回结果。如果 input-selectors 的值和前一次的一样,它将会直接返回前一次计算的数据,而不会再调用一次转换函数。

定义一个可记忆的 selector getVisibletodos 来替代上面的无记忆版本:

selectors/index.js
import { createSelector } from 'reselect'
 
const getVisibilityFilter = state => state.visibilityFilter
const getTodos = state => state.todos
 
export const getVisibletodos = createSelector(
  [getVisibilityFilter, getTodos],
  (visibilityFilter, todos) => {
    switch (visibilityFilter) {
      case 'SHOW_ALL':
        return todos
      case 'SHOW_COMPLETED':
        return todos.filter(t => t.completed)
      case 'SHOW_ACTIVE':
        return todos.filter(t => !t.completed)
    }
  }
)
在上例中,getVisibilityFilter 和 getTodos 是 input-selector。因为他们并不转换数据,所以被创建成普通的非记忆的 selector 函数。但是,getVisibletodos 是一个可记忆的 selector。他接收 getVisibilityFilter 和 getTodos 为 input-selector,还有一个转换函数来计算过滤的 todos 列表。

组合 Selector
可记忆的 selector 自身可以作为其它可记忆的 selector 的 input-selector。下面的 getVisibletodos 被当作另一个 selector 的 input-selector,来进一步通过关键字(keyword)过滤 todos。

const getKeyword = state => state.keyword
 
const getVisibletodosFilteredByKeyword = createSelector(
  [getVisibletodos, getKeyword],
  (visibletodos, keyword) =>
    visibletodos.filter(todo => todo.text.indexOf(keyword) > -1)
)
连接 Selector 和 Redux Store
如果你在使用 React Redux,你可以在 mapStatetoProps() 中当正常函数来调用 selectors

containers/VisibletodoList.js
import { connect } from 'react-redux'
import { toggletodo } from '../actions'
import TodoList from '../components/TodoList'
import { getVisibletodos } from '../selectors'
 
const mapStatetoProps = state => {
  return {
    todos: getVisibletodos(state)
  }
}
 
const mapdispatchToProps = dispatch => {
  return {
    onTodoClick: id => {
      dispatch(toggletodo(id))
    }
  }
}
 
const VisibletodoList = connect(
  mapStatetoProps,
  mapdispatchToProps
)(TodoList)
 
export default VisibletodoList
在 selectors 中访问 React Props
到目前为止,我们只看到 selector 接收 Redux store state 作为参数,然而,selector 也可以接收 props。

例如,我们来实现包含多个 Todo List 的应用。我们需要改写 state 来支持多个 Todo List,每个 Todo List 分别有各自的 todos 和 visibilityFilter state。


我们还需要改写 reducer,现在 todos 和 visibilityFilter 分别在各自的 Todo List state 里, 所以我们只需要一个 todoLists reducer 来进行我们的 state 管理。

reducers/index.js
import { combineReducers } from 'redux'
import todoLists from './todoLists'
 
export default combineReducers({
  todoLists
})
reducers/todoLists.js
// Note that we're hard coding three lists here just as an example.
// In the real world, we'd have a feature to add/remove lists,
// and this would be empty initially.
const initialState = {
  1: {
    todos: [],
    visibilityFilter: 'SHOW_ALL'
  },
  2: {
    todos: [],
    visibilityFilter: 'SHOW_ALL'
  },
  3: {
    todos: [],
    visibilityFilter: 'SHOW_ALL'
  }
}
 
const addTodo = (state, action) => {
  const todoList = state[action.listId]
  const { todos } = todoList
 
  return {
    ...state,
    [action.listId]: {
      ...todoList,
      todos: [
        ...todos,
        {
          id: action.id,
          text: action.text,
          completed: false
        }
      ]
    }
  }
}
 
const toggletodo = (state, action) => {
  const todoList = state[action.listId]
  const { todos } = todoList
 
  return {
    ...state,
    [action.listId]: {
      ...todoList,
      todos: todos.map(todo =>
        (todo.id === action.id)
          ? {...todo, completed: !todo.completed}
          : todo
      )
    }
  }
}
 
const setVisibilityFilter = (state, action) => {
  const todoList = state[action.listId]
  return {
    ...state,
    [action.listId]: {
      ...todoList,
      visibilityFilter: action.filter
    }
  }
}
 
export default const todoLists = (state = initialState, action) => {
  // make sure a list with the given id exists
  if (!state[action.listId]) {
    return state;
  }
 
  switch (action.type) {
    case 'ADD_Todo':
      return addTodo(state, action)
 
    case 'TOGGLE_Todo':
      return toggletodo(state, action)
 
    case 'SET_VISIBILITY_FILTER':
      return setVisibilityFilter(state, action)
 
    default:
      return state
  }
}
上面的例子中,我们使用 todoLists reducer 来处理全部三个 action, 所以我们需要向 action creator 传入一个 listId 参数

actions/index.js
let nextTodoId = 0
export const addTodo = (text, listId) => ({
  type: 'ADD_Todo',
  id: nextTodoId++,
  text,
  listId
})
export const setVisibilityFilter = (filter, listId) => ({
  type: 'SET_VISIBILITY_FILTER',
  filter,
  listId
})
export const toggletodo = (id, listId) => ({
  type: 'TOGGLE_Todo',
  id,
  listId
})
export const VisibilityFilters = {
  SHOW_ALL: 'SHOW_ALL',
  SHOW_COMPLETED: 'SHOW_COMPLETED',
  SHOW_ACTIVE: 'SHOW_ACTIVE'
}
components/TodoList.js
import React from 'react'
import PropTypes from 'prop-types'
import Todo from './Todo'
const TodoList = ({ todos, toggletodo, listId }) => (
  <ul>
    {todos.map(todo => (
      <Todo
        key={todo.id}
        {...todo}
        onClick={() => toggletodo(todo.id, listId)}
      />
    ))}
  </ul>
)
 
export default TodoList
以下是渲染三个 VisibletodoList components 的 App , 每个VisibletodoList 都有一个 listId prop。

components/App.js
import React from 'react'
import Footer from './Footer'
import AddTodo from '../containers/AddTodo'
import VisibletodoList from '../containers/VisibletodoList'
 
const App = () => (
  <div>
    <VisibletodoList listId="1" />
    <VisibletodoList listId="2" />
    <VisibletodoList listId="3" />
  </div>
)
每个 VisibletodoList 容器根据 listId props 的值选择不同的 state 切片,让我们修改 getVisibilityFilter 和 getTodos 来接收 props。

selectors/todoSelectors.js

import { createSelector } from 'reselect'

const getVisibilityFilter = (state, props) =>
 state.todoLists[props.listId].visibilityFilter

const getTodos = (state, props) => state.todoLists[props.listId].todos

const getVisibletodos = createSelector(
 [getVisibilityFilter, getTodos],
 (visibilityFilter, todos) => {
   switch (visibilityFilter) {
     case 'SHOW_COMPLETED':
       return todos.filter(todo => todo.completed)
     case 'SHOW_ACTIVE':
       return todos.filter(todo => !todo.completed)
     default:
       return todos
   }
 }
)

export default getVisibletodos

props 可以通过 mapStatetoProps 传递给 getVisibletodos:

const mapStatetoProps = (state, props) => {
  return {
    todos: getVisibletodos(state, props)
  }
}
现在,getVisibletodos 可以访问 props,一切看上去都是如此的美好。

但是这儿有一个问题!

使用带有多个 visibletodoList 容器实例的 getVisibletodos selector 不能正常使用函数记忆功能。

containers/VisibletodoList.js
import { connect } from 'react-redux'
import { toggletodo } from '../actions'
import TodoList from '../components/TodoList'
import { getVisibletodos } from '../selectors'
 
const mapStatetoProps = (state, props) => {
  return {
    // 警告:下面的 selector 不会正确记忆
    todos: getVisibletodos(state, props)
  }
}
 
const mapdispatchToProps = dispatch => {
  return {
    onTodoClick: id => {
      dispatch(toggletodo(id))
    }
  }
}
 
const VisibletodoList = connect(
  mapStatetoProps,
  mapdispatchToProps
)(TodoList)
 
export default VisibletodoList
用 createSelector 创建的 selector 只有在参数集与之前的参数集相同时才会返回缓存的值。如果我们交替的渲染 VisibletodoList listId="1" /> 和 VisibletodoList listId="2" />,共享的 selector 将交替的接收 listId: 1 和 listId: 2。这会导致每次调用时传入的参数不同,因此 selector 将始终重新计算而不是返回缓存的值。我们将在下一节了解如何解决这个限制。

(编辑:汽车网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章