React 组件通信props 以及 state

97 阅读6分钟

props

1. 使用

React主要使用props来互相通信,props有点类似于vue中的父子组件传值,实际上React也是类似的。
因为React的特性就是具有单向数据流,所以主要作用就是父组件向子组件中传递数据。 React核心思想是组件化思想,页面被切分成独立、可复用组件。组件从概念上看就是一个函数,既然是函数,就需要接受参数。而这个参数就是props,props理解为从外部传入组件内部的数据。 props不仅可以传字符串,数字,还可以传递对象,数组和回调函数。举例如下

类组件:
class Welcome extends React.Component {
  render() {
    return <h1>Hello {this.props.name}</h1>
  }
}

const element = <Welcome name="Jack" onNameChanged={this.handleName} />

上述name属性和onNameChanged方法都能在子组件的props中访问。

函数式组件:
import { getUrl } from './utils.js';

function Example({ person, size }) {
  return (
    <img
      className="example"
      src={getUrl(person)}
      alt={person.name}
      width={size}
      height={size}
    />
  );
}

export default function ExportExam() {
  return (
    <div>
      <Example
        size={100}
        person={{ 
          name: 'Lily', 
          imageId: '88dsfaddsf'
        }}
      />
    </div>
     <Example
        size={50}
        person={{ 
          name: 'Jack', 
          imageId: '000dfdsa'
        }}
      />
    </div>
  );
}

刚才提到,props类似于向函数中传递的参数,向person和size props渲染的Example中添加一些数据,就能通过不同的props,多次以不同方式渲染。改变person和size,无需考虑Example如何使用这些数据。

2. 解构

!注意:function Example({ person, size }) 中的括号不能忘记,这种语法被称为“解构”,等价于从函数参数中读取属性。

function Example({ person, size }){
// ...
} 等价于
 function Example(props) {
    let person = props.person;
    let size = props.size;
 }

3. 指定默认值

如果想给prop指定默认值,可以在参数后面指定,默认值仅在缺少size prop时或size={undefined}时生效,如果传递了size={null}或size={0},默认值将不被使用,如:

function Example({ person, size = 100}){
// ...
}

注意:单向数据流的特点之一就是,props在内部不可变,想改变只能通过外部组件传入新的props来重新渲染子组件,否则子组件的props和展示形式不变。实际上这也是为React带来安全性的特点之一,props相关数据不能在内部随意变更。

总结:

  • 1.可以通过解构读取props,记得用{}包裹解构的变量
  • 2.可以指定默认值用于缺少值或者值为undefined的情况的props
  • 3.可以使用<Example {...props}/>JSX语法展开props,但不要过度使用
  • 4.props是不可变的单向数据流,需要交互和改变的时候需要用到state。

state

1.state的作用

一个组件的显示形态可以由数据状态和外部参数决定,外部参数就是上面提到的props,而数据状态就是state,一般在constructor中初始化。 之前说到props的值是不可变的,需要交互和改变的时候要用到state,那么state如何实现值的变化呢?

export default function Example() {
  let index = 0;

  function handleClick() {
    index = index + 1;
  }

  return (
    <>
      <button onClick={handleClick}>
        Next
      </button>
      <div>{index}<div/>
    </>
  );
}

点击按钮时,handleClcik应该将index加1,然而变化并未生效。有两个原因: 1.局部变量无法在多次渲染中持久保存。React重新渲染组件时会从头渲染,不考虑之前对局部变量的更改。 2.更改局部变量并没有触发渲染,即React没有意识到要重新渲染。

所以,需要用新的数据渲染组件,需要对应两个变化:
1.要么保存变化的数据。
2.要么触发React用新数据重新渲染。

而useState作为React的Hook正是提供了这两种功能:
1.用State变量保存变化数据。
2.State setter函数用于更新变量并触发重新渲染。

举例:

//从文件中引入该Hook
import {useState} from 'react'

export default function Example() {
  //let index = 0;
  //声明state变量
  const [index,setIndex] = useState(0);

  function handleClick() {
  //更新index,并触发重新渲染
    setIndex(index + 1);
  }

  return (
    <>
      <button onClick={handleClick}>
        Next
      </button>
      <div>{index}<div/>
    </>
  );
}

注意:useState等其他以“use”开头的函数都被称为Hook。Hook是一种特殊的函数,只在React渲染的时候生效。Hooks这些以use开头的函数只能在组件或自定义Hook的最顶层调用。不能在条件语句,循环语句或其他嵌套函数内调用Hook,Hook是函数,在顶部使用,类似于在文件顶部导入模块。

2.本质

  1. 组件第一次渲染,将0作为index的初始值传给useState,返回的是[0,setIndex].
  2. 更新state时,调用setIndex(index+1)。index是0,所以setIndex(1),告诉React现在记住index是1并触发下一次渲染。
  3. 组件第二次渲染。React仍然看到useState(0),但是React记住了index变成了1,所以返回[1,setIndex]。
  4. 以此类推。

3.state是隔离且私有的

当渲染一个组件两次,每个副本都会有完全隔离的state,改变其中一个不会影响另一个。

//从文件中引入该Hook
import {useState} from 'react'

export default function Example() {
  //let index = 0;
  //声明state变量
  const [index,setIndex] = useState(0);

  function handleClick() {
  //更新index,并触发重新渲染
    setIndex(index + 1);
  }

  return (
    <>
      <button onClick={handleClick}>
        Next
      </button>
      <div>{index}<div/>
      
      <button onClick={handleClick}>
        Next
      </button>
      <div>{index}<div/>
    </>
  );
}

渲染了两个按钮点击变化显示值的代码,它们的index是分别存储的,不会互相影响。如果想要两个index同步的话,从子组件中删除,添加到离它们最近的父组件中进行状态提升。

4.组件间共享state

我们知道state的特性是组件私有的,两个地方渲染它,每个副本拥有自己的state,互相独立。 所以要做到共享state,需要进行状态提升,将状态提升到它们的父组件中。

    1. 从子组件中移除state
    1. 从父组件中传递硬编码数据。
    1. 为共同的父组件添加state,将其与时间处理函数一起向下传递。 如下所示:
//从文件中引入该Hook
import {useState} from 'react'

export default function Example() {
  //let index = 0;
  //声明state变量
  const [index,setIndex] = useState(0);

  function handleClick() {
  //更新index,并触发重新渲染
    setIndex(index + 1);
  }

  return (
    <>
     <Share index={index} />
    </>
  );
}

function Share({index}) {
  return (
    <>
      <button onClick={handleClick}>
        Next
      </button>
      <div>{index}<div/>
      
      <button onClick={handleClick}>
        Next
      </button>
      <div>{index}<div/>
    </>
  );
}

注:如果想要整合两个组件,将state移动到共同的父组件中,在父组件中通过props把信息传递下去。向下传递事件。当组件由prop驱动,则称为受控组件;由state驱动称为不受控组件。

总结

  1. 如果一个组件需要在多次渲染期间记住某些信息时使用state变量。
  2. state变量是通过调用useState Hook来声明的。
  3. Hook是以use开头的特殊的函数,该函数仅仅在组件或另一个Hook的顶层调用才有效,不能在条件语句,循环语句或其他嵌套函数内调用Hook。
  4. state是组件私有的,两个地方渲染它,每个副本拥有自己的state,互相独立。

prop和state对比

相同点:

  • 两者都是JavaScript对象。
  • 两者都是用于保存信息。
  • props和state都能触发渲染更新。

区别:

  • props是外部传递给组件的,state是在组件内被组件自己管理的。
  • props在组件内部不可修改,state在组件内部可以进行修改。
  • props是组件内公有的,state是组件私有的,两个地方渲染它,每个副本拥有自己的state,互相独立。要想让state变成组件公有的,需要进行状态提升,将状态提升到父组件。