Duan Hong +

reselect for redux state (2)

接上篇 reselect fo redux state

Source Code

先回顾下上篇中的知识点和遗留的问题:

知识点
1. redux 单项数据流的实现,action -> reducer -> store -> component
2. setState 与 action 协作方式的不同,后者可以缓存当前组件的信息。
3. 对 state 进行统一管理

问题
1. 切换页面时,render 中数据的重复计算
2. 如何更好的缓存数据

关于 redux 的问题,我和同事的想法有很多不同。


关于 Reselect

  • Selectors can compute derived data, allowing Redux to store the minimal possible state.
  • Selectors are efficient. A selector is not recomputed unless one of its arguments change.
  • Selectors are composable. They can be used as input to other selectors.

最开心的莫过于,遇到问题的时候,Google 出来类似的话了,so 开搞吧。

➜  src git:(master) ✗ vi app/selectors/movies.js
➜  src git:(master) ✗ more app/selectors/movies.js
import { createSelector } from 'reselect'

const getMovies = state => state.home.movies

const getLoading = state => state.home.loading

const getSort = state => state.home.filter.sort

const getSearch = state => state.home.filter.search

export default createSelector(
    getMovies,
    getLoading,
    getSort,
    getSearch,
    (movies, loading, sort, search) => {
        // first filter
        let _movies = search.length ?
            movies.filter(m => m.show_title.toLowerCase().indexOf(search.toLowerCase()) !== -1) :
            [...movies]

        // then sort
        _movies = sort === 'default' ?
            _movies :
            _movies.sort((m1, m2) => m1[sort] < m2[sort])

        console.log('computed', Date.now());

        // final data structure we wanted.
        return {
            sort,
            search,
            movies: _movies,
            loading,
        }
    }
)
➜  src git:(master) ✗

由于精简了最终返回的数据结构,抽离了业务数据的计算,所以,组件层面要稍微调整下:

import styles from './movies';
// import { component_name } from 'wepiao'
import connect from 'utils/connect'
import MoviesSelector from 'app/selectors/movies'

@connect(MoviesSelector)
export default class Movies extends React.Component {

    componentWillMount() {
        // use cached resource at necessary
        this.props.loading && this.props.actions.fetchMovies() // use default actor name
    }

    componentDidMount() {
        let { sort, search } = this.props
        this.refs.input.value = search
        this.refs.select.value = sort
    }

    onChangeSort(e) {
        console.log(e.target.value);
        this.props.actions.filterMovies({ sort: e.target.value })
    }

    onChangeSearch(e) {
        console.log(e.target.value)
        this.props.actions.filterMovies({ search: e.target.value })
    }

    // filterAndSortMovies(movies) {
    //     let { sort, search } = this.props.home.filter
    //     console.log(sort, search);
    //     let _movies = search.length ?
    //         movies.filter(m => m.show_title.toLowerCase().indexOf(search.toLowerCase()) !== -1) :
    //         movies
    //     if(sort === 'default') {
    //         return _movies
    //     } else {
    //         return _movies.sort((m1, m2) => m1[sort] < m2[sort])
    //     }
    // }

    render() {
        let { loading, movies } = this.props

        // movies = this.filterAndSortMovies(movies)
        
        ...
    }

经过 reselect 这一层,component 得到的 props 结构是这样的

注意,我在 selector 返回最终数据的函数中,插入了一段 log 用于记录从新计算的过程

console.log('computed', Date.now());

在下图中,当页面刷新的时候,有两次计算过程,一次是 API 请求之前,一次发生在拿到 API 返回的数据之后,这是正常的逻辑。之后,在页面前进和后退的过程中,没有再看到计算的日志。由此,我们的目的就达到了。

此中情况的关键还在于,调用 action 的时机。这里,我处理的有点牵强

componentWillMount() {
    // use cached resource at necessary
    this.props.loading && this.props.actions.fetchMovies() // use default actor name
}

然而,这确实缓存利用的关键所在,它决定了原始数据能否重新利用。从根源处判断,才是解决缓存问题的关键所在。

之前,也一直纠结在这块逻辑上,如何利用意境计算出来的数据(已经缓存在 store 中了嘛)。请求层面的缓存,只是解决了 HTTP 时延的问题,但前端的渲染前的计算缓存,同样重要,试想,整个 app 都用 stateless components 搭建,性能岂不爆表,这才是将缓存发挥到极致。API 层数据缓存(session),UI 层业务模型缓存(reselect),react 层 DOM 缓存(virtual DOM), 哈哈,这么多层,性能上不去才怪呢!

Source Code


小结

到上面这一步,磕磕绊绊,半年了。现在的前端,日新月异,技术层面日益多元化。就现在这个项目骨架,就涉及到下面这么多东西,这,对一个新人来说,很难接受,也无从着手。这块,也是不得不考虑的一点。

resource description
react UI library
react-router react router component
redux react flux implementation, more elegant ang powerful
redux-thunk The easiest way to write async action creators
redux-logger Log every Redux action and the next state
react-router-redux Ruthlessly simple bindings to keep React Router and Redux in sync
reselect Efficient derived data selectors inspired by NuclearJS
react-redux react binding
whatwg-fetch fetch polyfill
pepper build tools, based on webpack, mainly for react projects

另外一点,开源组件的依赖中,你都知道其功能特点吗?还是只是参照官方 Demo 照搬过来的?

关于我,现在从事前端基础工程建设的工作,包含性能相关,工具库和UI库的搭建等工作,有时候不免在前沿技术层面走的太快,而忽略了真正的使用者的需求,忽视了他们的立场。所以,就想着把这些东西封装起来(that’s pepper),让使用变的简单。

这就好比 IDE 与 VI 的区别,喜欢的爱的死去活来,不喜欢的嗤之以鼻。

前路的一扇窗户,照亮着你前行的路,走近它,能看到更宽广的天空,打开它,你看到的是整个世界。

Stay Hungary, Stay Foolish

Wanna say something ?

Blog

Friends