您好, 欢迎来到 !    登录 | 注册 | | 设为首页 | 收藏本站

搭配 React

这里需要再强调一下:R 和 React 之间没有关系。R React、Angular、Ember、jQuery 甚至纯 JavaScript。

尽管如此,R 还是和  和  这类库搭配起来用最好,因为这类库允许你以 state 的形式来描述界面,R 通过 action 的形式来发起 state 变化。

下面使用 React 来开发 todo 任务管理应用。

安装 React R

R 认并不包含 ,需要单独安装。

npm install --save react-r

如果你不使用 npm,你也可以从 unpkg 最新的 UMD 包(和)。如果你用 <script> 的方式引入 UMD 包,那么它会在全局抛出window.ReactR对象。

R 的 React 绑定库是基于  的开发思想。所以建议先读完这篇再回来继续学习。这个思想非常重要。

已经读完了?那让我们再总结一下不同点:

大部分的组件都应该是展示型的,但一般需要少数的几个容器组件把它们和 R store 连接起来。这和下面的设计简介并不意味着容器组件必须位于组件树的最顶层。如果容器组件变得太复杂(例如,它有大量的嵌套组件以及传递数不尽的回调),那么在组件树中引入另容器,就像FAQ中提到的那样

技术上讲你可以直接使用 store.subscribe() 来编写容器组件。但不建议这么做的原因是无法使用 React R 带来的优化。也因此,不要手写容器组件,而使用 React R 的 connect() 来,后面会详细介绍。

设计组件层次结构

还记得当初如何设计state根对象的结构 吗?现在就要定义与它匹配的界面的层次结构。其实这不是 R 相关的工作,方面解释的非常棒。

我们的概要设计很简单。我们想要 todo 项的列表。 todo 项被点击后,会一条线并 completed。我们会新增 todo 字段。在 footer 里可切换的全部/只 completed 的/只 incompleted 的 todos。

以下的这些组件(和它们的 props )就是从这个设计里来的:

TodoList 用于 todos 列表。

todos: Array 以 { text, completed } 形式的 todo 项数组。

onTodoClick(index: number) 当 todo 项被点击时的回调。

Todo  todo 项。

text: string 的文本。

completed: boolean todo 项是否线。

onClick() 当 todo 项被点击时的回调。

Link 带有 callback 回调的

onClick() 当点击时会触发

Footer 允许改变可见 todo 过滤器的组件。

App 根组件,渲染余下的所有。

这些组件只定义外观并不关心数据来源和如何改变。传入什么就渲染什么。如果你把从 R 迁移到别的架构,这些组件可以不做任何改动直接使用。它们并不依赖于 R。

还需要一些容器组件来把展示组件连接到 R。例如,展示型的 TodoList 组件需要类似 VisibleTodoList 的容器来监听 R store 变化并处理如何过滤出要的数据。为了实现状态过滤,需要实现 FilterLink 的容器组件来渲染 Link 并在点击时触发对应的 action:

VisibleTodoList 根据当前的状态来对 todo 列表进行过滤,并渲染 TodoList。

FilterLink 得到当前过滤器并渲染 Link。

filter: string 就是当前过滤的状态

有时很难分清到底该使用容器组件还是展示组件。例如,有时表单和严重耦合在一起,如这个小的组件:

AddTodo 含有“Add”按钮的输入框

技术上讲可以把它分成两个组件,但一开始就这么做有点早。在一些非常小的组件里混用容器和展示是可以的。当业务变复杂后,如何拆分就很明显了。所以现在就使用混合型的吧。

终于开始开发组件了!先做展示组件,这样可以先不考虑 R。

它们只是普通的 React 组件,所以不会详细解释。我们会使用式无状态组件除非需要本地 state 或生命周期的场景。这并不是说展示组件必须是 -- 只是因为这样做容易些。如果你需要使用本地 state,生命周期,或者优化,可以将它们转成 class。

import React from 'react'
import PropTypes from 'prop-types'

const Todo = ({ onClick, completed, text }) => (
  <li
    onClick={onClick}
    style={{
      textDecoration: completed ? 'line-through' : 'none'
    }}
  >
    {text}
  </li>
)

Todo.propTypes = {
  onClick: PropTypes.func.is,
  completed: PropTypes.bool.is,
  text: PropTypes.string.is
}

export default Todo
import React from 'react'
import PropTypes from 'prop-types'
import Todo from './Todo'

const TodoList = ({ todos, onTodoClick }) => (
  <ul>
    {todos.map((todo, index) => (
      <Todo key={index} {...todo} onClick={() => onTodoClick(index)} />
    ))}
  </ul>
)

TodoList.propTypes = {
  todos: PropTypes.arrayOf(
    PropTypes.shape({
      id: PropTypes.number.is,
      completed: PropTypes.bool.is,
      text: PropTypes.string.is
    }).is
  ).is,
  onTodoClick: PropTypes.func.is
}

export default TodoList
import React from 'react'
import PropTypes from 'prop-types'

const Link = ({ active, children, onClick }) => {
  if (active) {
    return <span>{children}</span>
  }

  return (
    <a
      href=""
      onClick={e => {
        e.preventDefault()
        onClick()
      }}
    >
      {children}
    </a>
  )
}

Link.propTypes = {
  active: PropTypes.bool.is,
  children: PropTypes.node.is,
  onClick: PropTypes.func.is
}

export default Link
import React from 'react'
import FilterLink from '../containers/FilterLink'

const Footer = () => (
  <p>
    Show: <FilterLink filter="SHOW_ALL">All</FilterLink>
    {', '}
    <FilterLink filter="SHOW_ACTIVE">Active</FilterLink>
    {', '}
    <FilterLink filter="SHOW_COMPLETED">Completed</FilterLink>
  </p>
)

export default Footer

现在来创建一些容器组件把这些展示组件和 R 关联起来。技术上讲,容器组件就是使用 store.subscribe() 从 R state 树中读取部分数据,并通过 props 来把这些数据提供给要渲染的组件。你可以手工来开发容器组件,但建议使用 React R 库的  来,这个做了优化来避免很多不必要的重复渲染。(这样你就不必为了而手动实现  中的 shouldComponentUpdate 。)

使用 connect() 前,需要先定义 mapStateToProps 这个来指定如何把当前 R store state 映射到展示组件的 props 中。例如,VisibleTodoList 需要计算传到 TodoList 中的 todos,所以定义了根据 state.visibilityFilter 来过滤 state.todos 的,并在 mapStateToProps 中使用。

const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case 'SHOW_COMPLETED':
      return todos.filter(t => t.completed)
    case 'SHOW_ACTIVE':
      return todos.filter(t => !t.completed)
    case 'SHOW_ALL':
    default:
      return todos
  }
}

const mapStateToProps = state => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
}

除了读取 state,容器组件还能分发 action。类似的方式,可以定义 mapDispatchToProps() 接收 dispatch() 并返回期望注入到展示组件的 props 中的回调。例如,我们希望 VisibleTodoList 向 TodoList 组件中注入叫 onTodoClick 的 props ,还希望 onTodoClick 能分发 TOGGLE_TODO 这个 action:

const mapDispatchToProps = dispatch => {
  return {
    onTodoClick: id => {
      dispatch(toggleTodo(id))
    }
  }
}

最后,使用 connect() 创建 VisibleTodoList,并传入这两个。

import { connect } from 'react-r'

const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

export default VisibleTodoList

这就是 React R API 的基础,但还漏了一些快捷技巧和强大的配置。建议你仔细学习 。如果你担心 mapStateToProps 创建新对象太过频繁,可以学习如何使用  来计算衍生数据。

其它容器组件定义如下:

import { connect } from 'react-r'
import { setVisibilityFilter } from '../actions'
import Link from '../components/Link'

const mapStateToProps = (state, ownProps) => {
  return {
    active: ownProps.filter === state.visibilityFilter
  }
}

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    onClick: () => {
      dispatch(setVisibilityFilter(ownProps.filter))
    }
  }
}

const FilterLink = connect(
  mapStateToProps,
  mapDispatchToProps
)(Link)

export default FilterLink
import { connect } from 'react-r'
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

回想一下前面提到的, AddTodo 组件的视图和逻辑混合在单独的定义之中。

import React from 'react'
import { connect } from 'react-r'
import { addTodo } from '../actions'

let AddTodo = ({ dispatch }) => {
  let input

  return (
    <div>
      <form
        onSubmit={e => {
          e.preventDefault()
          if (!input.value.trim()) {
            return
          }
          dispatch(addTodo(input.value))
          input.value = ''
        }}
      >
        <input
          ref={node => {
            input = node
          }}
        />
        <button type="submit">Add Todo</button>
      </form>
    </div>
  )
}
AddTodo = connect()(AddTodo)

export default AddTodo

如果你不熟悉 ref , 请阅读这篇以熟悉这个的推荐。

import React from 'react'
import Footer from './Footer'
import AddTodo from '../containers/AddTodo'
import VisibleTodoList from '../containers/VisibleTodoList'

const App = () => (
  <div>
    <AddTodo />
    <VisibleTodoList />
    <Footer />
  </div>
)

export default App

传入 Store

所有容器组件都可以访问 R store,所以可以手动监听它。一种方式是把它以 props 的形式传入到所有容器组件中。但这太麻烦了,因为必须要用 store 把展示组件包裹一层,仅仅是因为恰好在组件树中渲染了容器组件。

建议的方式是使用指定的 React R 组件  来  让所有容器组件都可以访问 store,而不必显式地传递它。只需要在渲染根组件时使用即可。

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-r'
import { createStore } from 'r'
import todoApp from './reducers'
import App from './components/App'

let store = createStore(todoApp)

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

接下来

参照本完整示例来深化理解。然后就可以跳到高级教程学习网络请求处理和路由。


联系我
置顶