实战分析:评论功能(七)

从本节开始,咱们开始用 Redux、React-redux 来重构第二阶段的评论功能。产品需求跟以前同样,可是会用 Redux、React-redux 来帮助管理应用状态,而不是“状态提高”。让整个应用更加接近真实的工程。html

你们能够在第二阶段的代码上进行修改 comment-app2(非高阶组件版本)。若是已经忘了第二阶段评论功能的同窗能够先简单回顾一下它的功能需求,实战分析:评论功能(四)。第1、2、三阶段的实战代码均可以在这里找到:react-naive-book-examples。react

咱们首先安装好依赖,如今 comment-app2 须要依赖 Redux、React-redux 了,进入工程目录执行命令安装依赖:npm

npm install redux react-redux --save

而后咱们二话不说先在 src 下创建三个空目录:componentscontainersreducersredux

构建评论的 reducer

咱们以前的 reducer 都是直接写在 src/index.js 文件里面,这是一个很差的作法。由于随着应用愈来愈复杂,可能须要更多的 reducer 来帮助咱们管理应用(这里后面的章节会有所说起)。因此最好仍是把全部 reducer 抽出来放在一个目录下 src/reducers数组

对于评论功能其实仍是比较简单的,回顾一下咱们在状态提高章节里面不断提高的状态是什么?其实评论功能的组件之间共享的状态只有 comments。咱们能够直接只在 src/reducers 新建一个 reducer comments.js 来对它进行管理。app

思考一下评论功能对于评论有什么操做?想清楚咱们才能写好 reducer,由于 reducer 就是用来描述数据的形态和相应的变动。新增和删除评论这两个操做是最明显的,你们应该都可以轻易想到。还有一个,咱们的评论功能其实会从 LocalStorage 读取数据,读取数据之后其实须要保存到应用状态中。因此咱们还有一个初始化评论的操做。因此目前能想到的就是三个操做:函数

// action types
const INIT_COMMENTS = 'INIT_COMMENTS'
const ADD_COMMENT = 'ADD_COMMENT'
const DELETE_COMMENT = 'DELETE_COMMENT'

咱们用三个常量来存储 action.type 的类型,这样之后咱们修改起来就会更方便一些。根据这三个操做编写 reducer:性能

// reducer
export default function (state, action) {
  if (!state) {
    state = { comments: [] }
  }
  switch (action.type) {
    case INIT_COMMENTS:
      // 初始化评论
      return { comments: action.comments }
    case ADD_COMMENT:
      // 新增评论
      return {
        comments: [...state.comments, action.comment]
      }
    case DELETE_COMMENT:
      // 删除评论
      return {
        comments: [
          ...state.comments.slice(0, action.commentIndex),
          ...state.comments.slice(action.commentIndex + 1)
        ]
      }
    default:
      return state
  }
}

咱们只存储了一个 comments 的状态,初始化为空数组。当遇到 INIT_COMMENTS 的 action 的时候,会新建一个对象,而后用 action.comments 覆盖里面的 comments属性。这就是初始化评论操做。测试

一样新建评论操做 ADD_COMMENT 也会新建一个对象,而后新建一个数组,接着把原来 state.comments 里面的内容所有拷贝到新的数组当中,最后在新的数组后面追加 action.comment。这样就至关新的数组会比原来的多一条评论。(这里不要担忧数组拷贝的性能问题,[...state.comments] 是浅拷贝,它们拷贝的都是对象引用而已。)优化

对于删除评论,其实咱们须要作的是新建一个删除了特定下标的内容的数组。咱们知道数组 slice(from, to) 会根据你传进去的下标拷贝特定范围的内容放到新数组里面。因此咱们能够利用 slice 把原来评论数组中 action.commentIndex 下标以前的内容拷贝到一个数组当中,把 action.commentIndex 坐标以后到内容拷贝到另一个数组当中。而后把两个数组合并起,就至关于“删除”了 action.commentIndex 的评论了。

这样就写好了评论相关的 reducer。

action creators

以前咱们使用 dispatch 的时候,都是直接手动构建对象:

dispatch({ type: 'INIT_COMMENTS', comments })

每次都要写 type 其实挺麻烦的,并且还要去记忆 action type 的名字也是一种负担。咱们能够把 action 封装到一种函数里面,让它们去帮助咱们去构建这种 action,咱们把它叫作 action creators。

// action creators
export const initComments = (comments) => {
  return { type: INIT_COMMENTS, comments }
}

export const addComment = (comment) => {
  return { type: ADD_COMMENT, comment }
}

export const deleteComment = (commentIndex) => {
  return { type: DELETE_COMMENT, commentIndex }
}

所谓 action creators 其实就是返回 action 的函数,这样咱们 dispatch 的时候只须要传入数据就能够了:

dispatch(initComments(comments))

action creators 还有额外好处就是能够帮助咱们对传入的数据作统一的处理;并且有了 action creators,代码测试起来会更方便一些。这些内容你们能够后续在实际项目当中进行体会。

整个 src/reducers/comments.js 的代码就是:

// action types
const INIT_COMMENTS = 'INIT_COMMENTS'
const ADD_COMMENT = 'ADD_COMMENT'
const DELETE_COMMENT = 'DELETE_COMMENT'

// reducer
export default function (state, action) {
  if (!state) {
    state = { comments: [] }
  }
  switch (action.type) {
    case INIT_COMMENTS:
      // 初始化评论
      return { comments: action.comments }
    case ADD_COMMENT:
      // 新增评论
      return {
        comments: [...state.comments, action.comment]
      }
    case DELETE_COMMENT:
      // 删除评论
      return {
        comments: [
          ...state.comments.slice(0, action.commentIndex),
          ...state.comments.slice(action.commentIndex + 1)
        ]
      }
    default:
      return state
  }
}

// action creators
export const initComments = (comments) => {
  return { type: INIT_COMMENTS, comments }
}

export const addComment = (comment) => {
  return { type: ADD_COMMENT, comment }
}

export const deleteComment = (commentIndex) => {
  return { type: DELETE_COMMENT, commentIndex }
}

有些朋友可能会发现咱们的 reducer 跟网上其余的 reducer 的例子不大同样。有些人喜欢把 action 单独切出去一个目录 actions,让 action 和 reducer 分开。我的观点以为这种作法可能有点过分优化了,其实多数状况下特定的 action 只会影响特定的 reducer,直接放到一块儿能够更加清晰地知道这个 action 其实只是会影响到什么样的 reducer。而分开会给咱们维护和理解代码带来额外没必要要的负担,这有种矫枉过正的意味。可是这里没有放之四海皆准的规则,你们能够多参考、多尝试,找到适合项目需求的方案。

我的写 reducer 文件的习惯,仅供参考:

  1. 定义 action types
  2. 编写 reducer
  3. 跟这个 reducer 相关的 action creators

相关文章
相关标签/搜索