前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React 进阶 - props

React 进阶 - props

作者头像
Cellinlab
发布2023-05-17 20:55:14
8330
发布2023-05-17 20:55:14
举报
文章被收录于专栏:Cellinlab's BlogCellinlab's Blog

# props

组件之间就像发动机的各个零件,想要让 React 这台机器运作起来,就要处理好各个零件,也就是各个组件之间的联系,而 props 担任的角色就是将每个组件联系起来。

props 是 React 组件通信最重要的手段,它在 React 的世界中充当的角色是十分重要的。

# props 是什么

对于在 React 应用中写的子组件,无论是函数组件 FunComponent ,还是类组件 ClassComponent ,父组件绑定在它们标签里的属性/方法,最终会变成 props 传递给它们。对于一些特殊的属性,比如说 ref 或者 key ,React 会在底层做一些额外的处理。

代码语言:javascript
复制
function ChildrenComponent() {
  return <div>ChildrenComponent</div>;
}

class PropsComponent extends React.Component {
  componentDidMount() {
    console.log('_this', this);
  }

  render () {
    const { children, mes, renderName, say, Component } = this.props;
    const renderFunction = children[0];
    const renderComponent = children[1];

    return <div>
      { renderFunction() }
      { mes }
      { renderName() }
      { renderComponent }
      <Component />
      <button onClick={() => say()}>change content</button>
    </div>
  }
}

class Index extends React.Component {
  state = {
    mes: 'hello world',
  }
  node = null
  say = () => {
    this.setState({
      mes: 'hello react',
    })
  }
  render() {
    return <div>
      <PropsComponent
        mes={this.state.mes}
        say={this.say}
        Component={ChildrenComponent}
        renderName={() => {
          return <div>renderName</div>
        }}
        >
        {() => {
          return <div>renderFunction</div>
        }}
        <ChildrenComponent />
      </PropsComponent>
    </div>
  }
}

props 可以作为:

  • 作为一个子组件渲染数据源 mes
  • 作为一个通知父组件的回调函数 say
  • 作为一个单纯的组件传递 ChildrenComponent
  • 作为渲染函数 renderName
  • render props renderFunction
  • render component 插槽组件 renderComponent

PropsComponent 如果是一个类组件,那么可以直接通过 this.props 访问到它:

在标签内部的属性和方法会直接绑定在 props 对象的属性上,对于组件的插槽会被绑定在 propschildren 属性中。

# React 如何定义 props

props 能做的事情:

  • 在 React 组件层级 props 充当的角色
    • 父组件 props 可以把数据层传递给子组件去渲染消费
    • 子组件可以通过 props 中的 callback ,来向父组件传递信息
    • 可以将视图容器作为 props 进行渲染
  • 从 React 更新机制中 props 充当的角色
    • props 在组件更新中充当了重要的角色,在 fiber 调和阶段中,diff 可以说是 React 更新的驱动器
    • 在 React 中,无法直接检测出数据更新波及到的范围,props 可以作为组件是否更新的重要准则,变化即更新,于是有了 PureComponentmemo 等性能优化方案
  • 从 React 插槽层面 props 充当的角色
    • React 可以把组件的闭合标签里的插槽,转化成 children 属性

# 监听 props 变化

类组件

  • componentWillReceiveProps 可以作为监听 props 的生命周期,但是 React 已经不推荐使用 componentWillReceiveProps,因为这个生命周期超越了 React 的可控制的范围内,可能引起多次执行等情况发生
  • 生命周期的替代方案 getDerivedStateFromProps

函数组件

  • 可以用 useEffect 来作为 props 改变后的监听函数(有一点值得注意, useEffect 初始化会默认执行一次)
代码语言:javascript
复制
function Index(props) {
  useEffect(() => {
    console.log('props.number change', props.number)
  }, [props.number])
  return <div>Index</div>
}

# props children 模式

props + children 模式 在 React 中非常常用,尤其对一些优秀开源组件库。比如 react-router 中的 SwitchRoute , antd 中的 FormFormItem

props + children 的基本应用:

props 插槽组件

代码语言:javascript
复制
<Container>
  <Children></Children>
</Container>

Container 组件中,通过 props.children 属性访问到 Children 组件,为 React element 对象,作用:

  • 可以根据需要控制 Children 组件的渲染
  • Container 可以用 React.cloneElement 强化 props 或者修改 Children 的子元素

render props 模式

代码语言:javascript
复制
<Container>
  {(ContainerProps) => <Children {...ContainerProps} />}
</Container>

Container 组件中,通过 props.children 属性访问到 Children 为一个函数,作用:

  • 可以根据需要控制 Children 组件的渲染
  • 可以将需要传给 Childrenprops 直接通过函数参数的方式传递给执行函数 children

混合模式

  • ContainerChildren 既有函数也有组件
代码语言:javascript
复制
<Container>
  {(ContainerProps) => <Children {...ContainerProps} />}
  <Children />
</Container>

  • 这种情况需要先遍历 children ,判断 children 元素类型:
    • 针对 element 节点,通过 cloneElement 混入 props
    • 针对函数,直接传递参数,执行函数

# props 使用小技巧

抽象 props

  • 抽象 props 一般用于跨层级传递 props ,一般不需要具体指出 props 中某个属性,而是将 props 直接传入或者是抽离到子组件中

混入 props

代码语言:javascript
复制
function Child(props) {
  console.log(props)
  return <div>hello, world</div>
}
function Parent(props) {
  const parentProps = {
    msg: 'Let us learn React'
  }
  return <Child {...props} {...parentProps} />
}
function Index() {
  const indexProps = {
    name: 'cell',
    age: 18
  }
  return <Parent {...indexProps} />
}

抽离 props

代码语言:javascript
复制
function Child(props) {
  console.log(props)
  return <div>hello, world</div>
}
function Parent(props) {
  const { msg, ...childProps } = props
  return <Child {...childProps} />
}
function Index() {
  const indexProps = {
    name: 'cell',
    age: 18,
    msg: 'Let us learn React'
  }
  return <Parent {...indexProps} />
}

注入 props

显式注入 props:能够直观看见标签中绑定的 props

代码语言:javascript
复制
function Child(props) {
  console.log(props)
  return <div>hello, world</div>
}
function Parent(props) {
  return props.children
}
function Index() {
  return <Parent>
    <Child name="cell" age={18} />
  </Parent>
}

隐式注入 props:一般通过 React.cloneElement 对 props.children 克隆再混入新的 props

代码语言:javascript
复制
function Child(props) {
  console.log(props)
  return <div>hello, world</div>
}
function Parent(props) {
  return React.cloneElement(props.children, {
    msg: 'Let us learn React'
  })
}
function Index() {
  return <Parent>
    <Child name="cell" age={18} />
  </Parent>
}

# 实践练习

实现一个 Demo ,用于表单状态管理的 <Form><FormItem> 组件:

  • <Form> 用于管理表单状态
  • <FormItem> 用于管理 <Input> 输入框组件

组件需要实现的功能:

  • Form 组件可以被 ref 获取实例
    • 可以调用实例方法 submitForm 获取表单内容,用于提交表单
    • resetForm 方法用于重置表单
  • Form 组件自动过滤掉除了 FormItem 之外的其他 React 元素
  • FormItemname 属性作为表单提交时候的 key ,还有展示的 label
  • FormItem 可以自动收集 <Input/> 表单的值

# <Form>

代码语言:javascript
复制
class Form extends React.Component {
  state = {
    formData: {}
  }
  submitForm = (cb) => {
    cb({...this.state.formData})
  }
  resetForm = () => {
    const { formData } = this.state;
    Object.keys(formData).forEach(key => {
      formData[key] = '';
    });
    this.setState({
      formData
    });
  }
  setValue = (name, value) => {
    this.setState({
      formData: {
        ...this.state.formData,
        [name]: value
      }
    });
  }
  render() {
    const { children } = this.props;
    const renderChildren = [];
    React.Children.forEach(children, (child) => {
      if (child.type.displayName === 'formItem') {
        const { name } = child.props;
        const Children = React.cloneElement(child, {
          key: name,
          handleChange: this.setValue,
          value: this.state.formData[name] || ''
        }, child.props.children);
        renderChildren.push(Children);
      }
    });
    return renderChildren;
  }
}
Form.displayName = 'form';

设计思路:

  • 考虑到 <Form> 在不使用 forwardRef 前提下,最好是类组件,因为只有类组件才能获取实例
  • 创建一个 state 下的 formData 属性,用于收集表单状态
  • 要封装 重置表单提交表单改变表单单元项的方法
  • 过滤掉除了 FormItem 元素之外的其他元素
    • 可以给函数组件或者类组件绑定静态属性来证明它的身份,然后在遍历 props.children 的时候就可以在 React element 的 type 属性(类或函数组件本身)上,验证这个身份
  • 要克隆 FormItem 节点,将改变表单单元项的方法 handleChange 和表单的值 value 混入 props

# <FormItem>

代码语言:javascript
复制
function FormItem(props) {
  const { children, name, handleChange, value, label } = props;
  const onChange = (value) => {
    handleChange(name, value);
  };
  return <div className='form'>
    <span className='label'>{label}:</span>
    {
      React.isValidElement(children) && children.type.displayName === 'input'
      ? React.cloneElement(children, {
          value,
          onChange
        })
      : null
    }
  </div>;
}
FormItem.displayName = 'formItem';

设计思路:

  • FormItem 一定要绑定 displayName 属性,用于让 <Form> 识别 <FormItem />
  • 声明 onChange 方法,通过 props 提供给 <Input> ,作为改变 value 的回调函数
  • FormItem 过滤掉除了 input 以外的其他元素

# <Input>

代码语言:javascript
复制
function Input({ onChange, value }) {
  return <input
    className='input'
    onChange={(e) => {
      onChange && onChange(e.target.value);
    }}
    value={value}
  />;
}
Input.displayName = 'input';

设计思路:

  • 绑定 displayName 标识 input
  • input DOM 元素,绑定 onChange 方法,用于传递 value

# 使用示例

代码语言:javascript
复制
export default () => {
  const form = React.useRef(null);
  const submit = () => {
    form.current.submitForm((formValue) => {
      console.log(formValue);
    });
  };
  const reset = () => {
    form.current.resetForm();
  };
  return <div>
    <Form ref={form}>
      <FormItem name='name' label='姓名'>
        <Input />
      </FormItem>
      <FormItem name='age' label='年龄'>
        <Input />
      </FormItem>
    </Form>
    <button onClick={submit}>提交</button>
    <button onClick={reset}>重置</button>
  </div>;
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022/9/11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • # props
    • # props 是什么
      • # React 如何定义 props
        • # 监听 props 变化
          • # props children 模式
            • # props 使用小技巧
            • # 实践练习
              • # <Form>
                • # <FormItem>
                  • # <Input>
                    • # 使用示例
                    相关产品与服务
                    容器服务
                    腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档