Talk is cheap. Show me your code

从 Vue 的视角学 React(四)—— 组件传参

组件化开发的时候,参数传递是非常关键的环节

哪些参数放在组件内部管理,哪些参数由父组件传入,哪些状态需要反馈给父组件,都需要在设计组件的时候想清楚

但实现这些交互的基础,是明白组件之间参数传递的方式,和各自的优缺点

 

一、父组件传参到子组件

和 Vue 一样,React 中从父组件到子组件的传参也是通过 props

不过在 Vue 项目中,需要在先组件里定义将要接收的 props,而 React 可以直接获取

而且 props 不光可以接收 Number、String 等基本类型,还可以接收 Function,这点在《从 Vue 的视角学 React(三)—— 事件处理》中也有提到

另外 props 还可以直接接收组件,类似于 Vue 中的 slot,后面会详细介绍

 

 

二、子组件向父组件传参

在项目中,也会有需要更新 props 的时候

但和 Vue 一样,React 中的 props 也是单向的,一定不能在组件内部直接修改 props 的值

而应该采用事件上报的方式,让父组件修改相应的状态

整个过程就像是 Vue 中的 $emit,只是将 $emit 中声明的自定义事件放到了 props 中

上面的 updateText 就是一个自定义事件,父组件为这个自定义事件添加了处理函数 handleUpdateText

如果子组件需要向父组件传递参数,可以在触发自定义事件 updateText 的时候,向事件处理函数传参

然后父组件在对应的事件处理函数 handleUpdateText 中获取到子组件传过来的参数

 

 

三、状态提升

除了父子之间的传参,还会有两个平级组件之间的参数传递

React 的推荐方案是,将两个子组件的参数都放到父组件中处理,这就是状态提升

 

假设有 boy 和 girl 两个组件,它们都是组件 father 的子组件

组件 girl 中有一个 food 状态,组件 boy 中有一个 age 状态,food 会随着 age 的变化而变化

如果 age 和 food 都是对应组件的 state,维护起来就比较麻烦

而如果将 age 和 food 都作为 props 传入,就可以在父组件 father 中维护这两个状态

当需要更新 age 的时候,从 boy 组件上报事件,然后在父组件中同时更新 food 和 age

 

React 是单向数据流,在设计组件的时候,应当始终保持自上而下的数据传递,而不是尝试在不同组件间同步 state

如果某些数据可以由 props 或 state 推导得出,那么它就不应该存在于 state 中

虽然这种方式会比双向绑定需要编写更多的代码,但更利于维护

 

 

四、限制 props 类型

良好的组件不可避免的会用到很多 props,为这些 props 添加类型校验就尤为重要

Vue 本身就有一套很完善的 props 类型校验配置,React 之前也有 React.PropTypes

但支持 TypeScript 之后,就将这部分功能拆成了独立的第三方库 prop-types

npm install prop-types -S

引入 PropTypes 之后,在组件内定义一个静态属性 propTypes(注意大小写),然后定义具体的规则

参考上图的示例,校验规则由  .  语法连接,如果有多个类型,就使用 oneOfType

更多关于 PropTypes 的使用可以查看官方文档

 

除了类型校验,有时候还需要给 props 添加默认值,这时候可以用 defaultProps 

 

 

五、组件嵌套

虽然开发组件的时候,我们总是希望能尽量满足需求,以减少后期的工作量

但一万个读者就有一万个哈姆雷特,一个组件不可能满足所有用户的需求,所以有时候就需要为组件提供一些扩展性

另外,还有一些组件本身就是作为容器开发的,这些场景都需要将组件作为 props 传给子组件

const Child = props => <div>{props.content}</div> 

const Tag = props => <div>This is tag</div>

const Parents = props => <Child content={<Tag />}/>

上面的代码中,子组件 Child 会接收一个 props 属性 content

然后在父组件 Parents 中,将 Tag 组件作为 content 的值传给了子组件 Child

 

不过这样的写法并不优雅,如果能像 Vue 的 slot 那样,直接将子组件嵌套在父组件的标签内,就更符合 HTML 标签的结构

这时候就可以用到 props.children

const Child = props => <div>{props.children}</div> 

const Tag = props => <div>This is tag</div>

const Parents = props => <Child><Tag /></Child>

从上面的代码可以看出,组件标签之间嵌套的内容,可以在组件内通过 props.children 接收到

事实上,props.children 是一个数组,如果不加具体的元素下标,就会将所有的元素渲染出来

如果标签内有多个节点,porps.children 就会将自身组件作为根节点,以数组的形式将组件内的 DOM 结构虚拟出来

而且 props.children 不单可以接收组件,还可以接收字符串

 

 

六、Context

在维护大型项目的时候,仅靠 props 和事件来传参是不够的,所以 Vue 提供了 Vuex 来维护状态

React 也有状态管理工具,常用的有 ReduxMobx,如何使用这些工具我会在以后的文章中介绍

但在使用这些工具之前,需要了解 context,因为 Redux 和 Mobx 都是基于 context 实现的

 

如果组件的层级很深,仅使用 props 层层嵌套传参的话就非常的冗余

这时如果有一个全局变量,在顶层组件定义之后,直接在底层组件中获取,就会非常简洁,context 就是这样的全局变量

// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
// 为当前的 theme 创建一个 context(“light”为默认值)。
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    // 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
    // 无论多深,任何组件都能读取这个值。
    // 在这个例子中,我们将 “dark” 作为当前的值传递下去。
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  // 指定 contextType 读取当前的 theme context。
  // React 会往上找到最近的 theme Provider,然后使用它的值。
  // 在这个例子中,当前的 theme 值为 “dark”。
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}

但 context 的使用会极大的增强组件之间的耦合性,项目中并不建议直接使用

所以我直接复制粘贴了官方文档的代码,仅为了解 context 这个概念

小型项目中,如果有深层次的传参,应当从组件设计上解决问题,比如直接将组件传下去

而大型项目中,如果需要用到 context,更推荐使用 redux 和 mobx 这些成熟的状态管理工具

 

 

参考资料:

《React 状态提升》

《React.js 的 context》

posted @ 2019-06-05 08:54  Wise.Wrong  阅读(...)  评论(... 编辑 收藏