React组件进阶

Posted webchang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React组件进阶相关的知识,希望对你有一定的参考价值。

react组件进阶

组件通讯介绍

组件是独立且封闭的单元,默认情况下,只能使用组件自己的数据。在组件化过程中,我们将一个完整的功能拆分成多个组件,以更好的完成整个应用的功能。而在这个过程中,多个组件之间不可避免的要共享某些数据。为了实现这些功能,就需要打破组件的独立封闭性,让其与外界沟通。这个过程就是组件通讯

组件的props

  • 组件是封闭的,接收外部数据应该通过props来实现
  • props的作用:接收传递给组件的数据
  • 接收数据:
    • 函数组件通过参数props来接收数据,props是一个对象
    • 类组件通过this.props接收数据
  • 传递数据:在组件标签上添加属性

函数组件

const Hello = (props) => 
    console.log(props);
    return (
        <div>props:props.name</div>
    )


// 渲染
ReactDOM.render(<Hello name="jack" />, document.getElementById('root'))

批量传递数据:

function Hello(props) 
  const name, age, gender = props;
  return (
    <h2>name-age-gender</h2>
  )


const data = 
  name: 'a',
  age: 19,
  gender: '女'


// 批量传递数据。这里的花括号表示内部是js表达式,正常情况下是不能直接写...data的,
// 如console.log(...data)就会报错。在react中允许这样做
ReactDom.render(<Hello ...data />, document.getElementById('root'));

类组件

class App extends React.Component 
 
    render() 
        console.log(this.props);
        return (
            <div>
                props: this.props.name
            </div>
        )
    


// 渲染
ReactDOM.render(<App name="jack" />, document.getElementById('root'))

注意:使用类组件时,如果写了构造函数,应该将props作为参数传递给constructor和super,否则无法在构造函数中通过this获取到props。不过即使没有传递props参数给constructor,在其它地方,比如render方法中,还是可以正常使用this.props的。

技术点:在继承类的构造函数中必须调用super函数,super代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数,否则会报错。但是super函数内的this指向的是当前类的实例。

构造器是否接受 props,是否传递给 super,取决于是否希望在构造器中通过 this访问props。

  • 当构造器中接收了props参数,super没有传递props,此时this.props是undefined,当然可以正常使用props(前边不加this)
  • 当构造器中接收了props参数,super也传递props,可以通过this.props拿到对象
class App extends React.Component 
  constructor(props) 
    super(props);
  
  render() 
    // constructor中有没有传递props,和这里没有关系
    return (
        <div>
          props:this.props.name
        </div>
    )
  

数据类型

  • 可以传递给组件任意类型的数据
  • props是只读的对象,只能读取属性的值,无法修改对象
import React from 'react'
import ReactDom from 'react-dom'

function Hello(props) 
  console.log(props);
  props.fn();
  return (
      <div>
        <h1>name:props.name</h1>
        props.tag
      </div>
  )


ReactDom.render(
    <Hello
        name="小明"
        age=18 // 如果要传递数字类型,需要使用包裹起来
        age2='8'
        colors=['red','green']
        fn=() => console.log('啊啊啊') // 可以传递函数
        tag=<p>这是一个p标签</p>         // 可以传递tag标签
    />, document.getElementById('root'));

Props 默认值为 “True”

如果你没给 prop 赋值,它的默认值是 true。以下两个 JSX 表达式是等价的:

<MyTextBox autocomplete />

<MyTextBox autocomplete=true />

props深入

children属性

  • children属性:表示组件标签的子节点。当组件标签有子节点时,props就会有该属性
  • children属性与普通的props一样,值可以是任意值(文本、React元素、组件,甚至是函数)
class App extends React.Component 
  render() 
    console.log(this.props);
    return (
        <div>
          <h2>组件标签的子节点:</h2>
          this.props.children // 我是子节点
        </div>
    )
  


ReactDom.render(
    <App name="hello">
      <p>我是子节点</p>
    </App>, document.getElementById('root'));

属性展开

如果你已经有了一个 props 对象,你可以使用展开运算符 ... 来在 JSX 中传递整个 props 对象。以下两个组件是等价的:

function App1() 
  return <Greeting firstName="Ben" lastName="Hector" />;


function App2() 
  const props = firstName: 'Ben', lastName: 'Hector';
  return <Greeting ...props />;

你还可以选择只保留当前组件需要接收的 props,并使用展开运算符将其他 props 传递下去。

const Button = props => 
  const  kind, ...other  = props;
  const className = kind === "primary" ? "PrimaryButton" : "SecondaryButton";
  return <button className=className ...other />;
;

const App = () => 
  return (
    <div>
      <Button kind="primary" onClick=() => console.log("clicked!")>
        Hello World!
      </Button>
    </div>
  );
;

在上述例子中,kind 的 prop 会被安全的保留,它将_不会_被传递给 DOM 中的 元素。 所有其他的 props 会通过 ...other 对象传递,使得这个组件的应用可以非常灵活。你可以看到它传递了一个 onClick 和 children 属性。

props校验

  • props校验:允许在创建组件的时候,就指定props的类型、格式等
  • 作用:捕获使用组件时因为props导致的错误,给出明确的错误提示,增加组件的健壮性

使用步骤

  1. 安装包prop-types npm i prop-types
  2. 导入prop-types包
  3. 使用**组件名.propTypes =**来给组件的props添加校验规则
  4. 校验规则通过PropTypes 对象来指定

约束规则:

  1. 常见类型: array、bool、func、number、object、string
  2. React元素类型: element
  3. 必填项:isRequired
  4. 自定义特定结构: shape( )

类组件中props校验的写法

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

class App extends React.Component 
  render() 
    console.log(this.props);
    let arr = this.props.colors;
    let list = arr.map((item, index) => <li key=index>index-item</li>)
    return (
        <ul>
          list
        </ul>
    )
  


// 添加props校验
App.propTypes = 
  colors: PropTypes.array,
  a: PropTypes.number, // 数值类型
  fn: PropTypes.func.isRequired, // 函数func并且必填,注意这里是func
  tag: PropTypes.element, // react元素
  filter: PropTypes.shape( // 自定义类型
    area: PropTypes.string,
    price: PropTypes.number
  )


// 当没有传递对应的prop时的默认值
App.defaultProps = 
  gender: '保密',
  age: 18

上边代码的写法是在类的外部给类加上自定义的属性,如何在类的内部就进行这些操作呢?类的静态属性 指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性。类的静态属性,写法是在实例属性的前面,加上static关键字。

class App extends React.Component 
  // 添加props校验
  static propTypes = 
    colors: PropTypes.array
  

// 当没有传递对应的prop时的默认值
  static defaultProps = 
    gender: '保密',
    age: 18
  

  render() 
    console.log(this.props);
    let arr = this.props.colors;
    let list = arr.map((item, index) => <li key=index>index-item</li>)
    return (
        <ul>
          list
        </ul>
    )
  

函数组件中props校验的写法

由于函数中不能使用static关键字,所以定义props校验的部分只能写在函数组件的外部。

function Hello(props) 
  const  name, age, gender  = props;
  return (
    <h2>name-age-gender</h2>
  )


// 提供类型限制
Hello.propTypes = 
  name: PropTypes.string.isRequired,
  age: PropTypes.number.isRequired


// 当没有传递对应的prop时的默认值
Hello.defaultProps = 
  gender: '保密',
  age: 18


const data = 
  name: 'a',
  age: 19


ReactDom.render(<Hello ...data />, document.getElementById('root'));

refs

过时 API:String 类型的 Refs

react之前的 API 中的 ref 属性是string 类型的,例如 “textInput”。可以通过 this.refs.textInput 来访问 DOM 节点。官方不建议使用它,因为 string 类型的 refs 存在 一些问题,效率不高。

class App extends React.Component 

  showData = () => 
    let input = this.refs.input1;
    alert(input.value)
  

  showData2 = (e) => 
    alert(e.target.value);
  

  render() 
    return (
      <div>
        <input ref="input1" type="text" />
        <button onClick=this.showData>提示</button>
        <input onBlur=this.showData2 type="text" />
      </div>
    )
  

回调refs

class App extends React.Component 

  state = 
    count: 0
  

  showData = () => 
    let input = this.input1;
    alert(input.value)
  

  updateCount = () => 
    this.setState(
      count: this.state.count + 1
    )
  

  render() 
    return (
      <div>
        /* 注意这里的ref是箭头函数,currentNode是当前节点,
        react内部会自动调用,然后将当前节点赋值给this的input1 */
        <input ref=currentNode =>  this.input1 = currentNode; console.log('--', currentNode)  type="text" />
        <button onClick=this.showData>提示</button>
        
        <h1>this.state.count</h1>
        <button onClick=this.updateCount>更新</button>
      </div>
    )
  

如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。

如图,每次更新时,ref的回调函数会被调用两次

将回调函数改为class的函数方式是这样的:

class App extends React.Component 

  showData = () => 
    let input = this.input1;
    alert(input.value)
  

  setInputRef = (c) => 
    console.log(c)
    this.input1 = c;
  

  render() 
    return (
      <div>
        <input ref=this.setInputRef type="text" />
        <button onClick=this.showData>提示</button>
      </div>
    )
  

React.createRef

React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是“专人专用”的

class App extends React.Component 
 
  inputRef = React.createRef();
  inputRef2 = React.createRef();

  showData = () => 
    let input = this.inputRef.current;
    console.log(input.value)
  

  showData2 = () => 
    console.log(this.inputRef2.current.value)
  

  render() 
    return (
      <div>
        /* react内部会将当前节点存储到this.inputRef中 */
        <input ref=this.inputRef type="text" />
        <button onClick=this.showData>提示</button>
        <input onBlur=this.showData2 ref=this.inputRef2 type="text" />
      </div>
    )
  

组件通讯总结

组件间的关系

  • 父子组件:props
  • 兄弟组件(非嵌套组件):消息订阅-发布、集中式管理、状态提升
  • 祖孙组件(跨级组件):消息订阅-发布、集中式管理、context
  1. props:
    1. children props
    2. render props
  2. 消息订阅-发布:pubs-sub、event等等
  3. 集中式管理:redux、dva等等
  4. context:生产者-消费者模式

父组件传递数据给子组件

  1. 父组件提供要传递的state数据
  2. 给子组件标签添加属性,值为state中的数据
  3. 子组件中通过props接收父组件中传递的数据

本质上和前边讲到的props的使用方式是一样的,只不过换了一种应用方式

class App extends React.Component 
  state = 
    lastName: '老王'
  

  render() 
    return (
        <div className="parent">
          父组件
          <Child name=this.state.lastName />
        </div>
    )
  


function Child(props) 
  return (
      <div className="child">
        <h1>子组件:props.name</h1>
      </div>
  )


ReactDom.render(<App />, document.getElementById('root'));

子组件传递数据给父组件

思路:利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数。

  1. 父组件提供一个回调函数(用于接收数据)
  2. 将该函数作为属性的值,传递给子组件
  3. 子组件通过props调用回调函数,将子组件的数据作为参数传递给回调函数
class App extends React.Component 
  state = 
    lastName: '老王',
    childData: ''
  

  // 1.父组件提供接收子组件数据的回调函数,参数就是子子组件传递过来

以上是关于React组件进阶的主要内容,如果未能解决你的问题,请参考以下文章

React组件进阶

React组件进阶

[React 进阶系列] React Context 案例学习:子组件内更新父组件的状态

React学习第五步:React 组件进阶

什么是React进阶组件?

React 函数组件与class组件的区别