目录

前言

一、类组件

1、定义类组件

2、认识props

3、认识props.children

二、State详解

1、认识state

2、React中事件处理的问题

1、事件处理的写法

2、React中关于this的问题

3、React中函数传参的问题

3、进一步了解state

1、state更改的问题

2、setState更新的问题

3、小案例

1、案例一:控制div的显示隐藏

2、案例二:随机颜色

三、组件间的传值

1、父向子传值

2、子向父传值

3、小案例

四、函数组件简介

1、纯函数

2、认识函数组件

1、函数组件的书写

2、函数组件的嵌套

五、相关面试题

1、setState的更新

2、setState异步更新的原因

3、类组件与函数组件的区别


前言

在文章开始前我们可以先简单的了解一下组件的概念,组件就是一段封装了html,css,js的代码,最终表现为一个自定义标签,组件都有自己的作用域。实际开发中函数组件用得很多,类组件现在用的很少,但是有些老项目可能用的是类组件,我们简单的了解一下就可以了。


一、类组件

类组件通过ES6类定义,继承内置的component类,并定义render方法

1、定义类组件

 <div class="title"></div>
  <script type="text/babel">
  const content=document.querySelector(".title")
  const root=ReactDOM.createRoot(content)
  class Welcome extends React.Component{
   render(){
    return <h1>我是react</h1>
   }
  }
  root.render(<><Welcome/></>)

  </script>

注:<></>是一个fragment节点,可以当成一个容器,不会生成额外的结构

2、认识props

props类似于函数的形参,可以让组件接收外面传来的数据,从而让组件展现不同的状态

class Welcome extends React.Component{
   render(){
    const {name}=this.props
    return <h1>{name}</h1>
   }
  }
  root.render(<><Welcome name="张三"/></>)

注:(1)变量不能再render函数外声明,render函数外是类的实例,可以定义属性和方法

(2)props在类组件内部是不能修改的,因为props接受的属性是从组件外传入的,并不是组件自己的数据,所以组件内部不能进行修改,如果要进行修改,只能修改数据源,重新传一个新的属性

(3)return后的Html标签要是换行写的话,必须要加小括号,否则会报错,不换行的话可以不用加小括号

class Welcome extends React.Component{
   render(){
    const {name}=this.props
    return (
<h1>{name}</h1>
)
   }
  }
  root.render(<><Welcome name="张三"/></>)

3、认识props.children

组件其实是一个html标签,可以是一个单标签,也可以是一个双标签,如果是一个双标签的话,就可以在标签内部写内容,可以通过props.children进行读取

传入多个内容,返回的是一个数组

   class Welcome extends React.Component{
    render(){
      console.log(this.props.children)
      return <>
      <p>hi</p>
      {this.props.children[0]}
      </>
    }
  }
  root.render(<><Welcome>
    <h1>我是react</h1>
    <p>hello</p>
    </Welcome></>)

传入单个内容返回的是一个对象

  class Welcome extends React.Component{
    render(){
      console.log(this.props)
      return <>
      <p>hi</p>
      {this.props.children}
      </>
    }
  }
  root.render(<><Welcome>
    <h1>我是react</h1>
    <p>hello</p>
    </Welcome></>)

二、State详解

1、认识state

state是组件私有的,属于局部变量,外部无法进行访问,state用于存储组件的动态数据,当组件的state更新时,组件会重新渲染,更新页面

 class Welcome extends React.Component{
    //写法一
    constructor(){
      super()
      this.state={
        msg:"123"
      }
    }
   //写法二
  state={
   msg:"123"
   }
    render(){
      console.log(this.props)
      return <>
      <p>hi</p>
      {this.state.msg}
      </>
    }
  }
  root.render(<>
  <Welcome/>
   </>)

注:state写在构造器函数内时,一定要调用super方法,不调用super方法,子类就得不到this对象

2、React中事件处理的问题

1、事件处理的写法

绑定事件要使用小驼峰,等号后面跟的是函数名

 class Welcome extends React.Component{
  fn(){
    console.log("123")
  }
    render(){
      return <>
      <button onClick={this.fn}>按钮</button>
      </>
    }
  }
  root.render(<>
  <Welcome/>
   </>)

2、React中关于this的问题

ES6的类会默认开启严格模式,this会指向undefined,react中的事件绑定方式,中间经历了一次赋值,就是把函数名赋值给了事件绑定函数,所以导致了this丢失

我们可以通过以下方法解决this丢失问题:

(1)在构造函数中改变this指向

 class Welcome extends React.Component{
    constructor(){
      super()
      this.fn=this.fn.bind(this)
    }
  fn(){
    console.log("123")
  }
    render(){
      return <>
      <button onClick={this.fn}>按钮</button>
      </>
    }
  }
  root.render(<>
  <Welcome/>
   </>)

(2)在函数调用的时候改变this指向

 class Welcome extends React.Component{
   fn(){
    console.log("123")
  }
    render(){
      return <>
      <button onClick={this.fn.bind(this)}>按钮</button>
      </>
    }
  }
  root.render(<>
  <Welcome/>
   </>)

(3)、箭头函数

箭头函数没有this,它的this默认与父级作用域的this指向相同,不会随意改变

 class Welcome extends React.Component{
   fn=()=>{
    console.log("123")
  }
    render(){
      return <>
      <button onClick={this.fn}>按钮</button>
      </>
    }
  }
  root.render(<>
  <Welcome/>
   </>)

3、React中函数传参的问题

1、匿名函数的形式

 class Welcome extends React.Component{
   fn=()=>{
    console.log("123")
  }
    render(){
      return <>
      <button onClick={()=>{this.fn(5)}}>按钮</button>
      </>
    }
  }
  root.render(<>
  <Welcome/>
   </>)

2、bind方式

 class Welcome extends React.Component{
   fn(){
    console.log("123")
  }
    render(){
      return <>
      <button onClick={this.fn.bind(this,5)}>按钮</button>
      </>
    }
  }
  root.render(<>
  <Welcome/>
   </>)

3、进一步了解state

1、state更改的问题

我们不能直接修改原state:react的设计要求state必须是不可变的,react通过比较新旧state的引用来判断是否需要重新渲染,如果直接修改原state,新旧state的引用是相同的,会导致react无法正确追踪状态的变化,会导致状态丢失或状态不一致,react会认为状态没有发生变化,从而会跳过必要的更新。

我们通过setState来修改state:setState可以确保react响应式系统正常工作,可以触发UI界面的渲染更新,保持状态的不可变性,支持浅比较优化,批处理,正确处理函数组件中的闭包问题,兼容并发渲染,提高性能。

1、修改state中基本数据类型

  class Welcome extends React.Component {
    state={
      num:5
    }
    changeNum=()=>{
      this.setState({
       num:5
      })
    }
    render(){
      const{num}=this.state
      return <>
      <p>{num}</p>
      <button onClick={this.changeNum}>按钮</button>
      </>
    }
    }
    root.render(<>
      <Welcome />
    </>)

2、修改state中数组对象

修改数组或对象,要先进行浅拷贝

  class Welcome extends React.Component {
      state = {
        num: 5,
        arr: [1, 2, 3],
        obj: { name: '小明', age: 18 }
      }
      changeNum=()=>{
        const nArr=[...this.state.arr,5]
        this.setState({
          num:6,
          arr:nArr,
          obj:{...this.state.obj,name:'张三'}
        })}
        render(){
          const {num,arr,obj}=this.state
          return <>
          <p>{num}</p>
          <h2>{arr}</h2>
          <h1>{JSON.stringify(obj)}</h1>
          </>
        }
    }
    root.render(<>
      <Welcome />
    </>)

2、setState更新的问题

setState同步更新出现的问题:setState如果是同步更新的,我们连续多次修改状态的话,就会频繁的进行dom操作,和不必要的中间渲染,例:如果我们多次调用点击事件,setState会多次更新渲染,但只有最后一次结果有意义,就会有很大的性能开销。

setState异步更新的好处:

(1)批量更新:将同一事件循环内的多个setState合并为一次更新,减少不必要的渲染

 class Welcome extends React.Component {
     state={
      num:1
     }
     add=()=>{
      let {num}=this.state
      for(let i=0;i<10;i++){
        console.log(num)//更新十次
        this.setState({
          num:num+1//更新一次
        })
      }
     }
     render(){
      let {num}=this.state
      return<>
      <p onClick={this.add}>{num}</p>
      </>
     }
    }
    root.render(<>
      <Welcome />
    </>)

如果不想合并多次更改state的操作,通过给setState传入函数的形式,返回新的状态

  class Welcome extends React.Component {
   state={
    num:2
   }
   add=()=>{
    let {num}=this.state
    this.setState((prev)=>{
      // prev代表上一次更新的状态
     return {
      num:prev.num+1
     }
    })
   
   }
     render(){
      let {num}=this.state
      return<>
      <p onClick={this.add}>{num}</p>
      </>
     }
    }
    root.render(<>
      <Welcome />
    </>)

(2)保证数据的一致性:异步更新会确保所有状态变化完后才统一渲染页面

点击p标签,页面显示的数据是5

   class Welcome extends React.Component {
   state={
    num:2
   }
   add=()=>{
    let {num}=this.state
    this.setState({
      num:4
    })
    this.setState({
      num:5
    })
   }
     render(){
      let {num}=this.state
      return<>
      <p onClick={this.add}>{num}</p>
      </>
     }
    }
    root.render(<>
      <Welcome />
    </>)

(3)支持并发模式

setState异步更新会允许react中断低优先级任务,优先处理高优先级任务

(4)在setState中如果想同步获取数据,可以使用setState的第二个参数,它的第二个参数是状态更新完的回调函数

 class Welcome extends React.Component {
      state = {
        num: 2
      }
      add = () => {
        let { num } = this.state
        for (let i = 0; i < 10; i++) {
          console.log(num)
          this.setState({
            num: num + 1
          }, () => { console.log(num) })//第二个参数是状态更新完的回调函数
        }

      }
      render() {
        let { num } = this.state
        return <>
          <p onClick={this.add}>{num}</p>
        </>
      }
    }
    root.render(<>
      <Welcome />
    </>)

3、小案例

1、案例一:控制div的显示隐藏
 class Welcome extends React.Component {
    state={
      display:"block"
    }
    changNum=()=>{
      let {display}=this.state
      this.setState({
        display:display==="none"?"block":"none"
      })
      }
      render(){
        let {display}=this.state
        return<>
        <div className="box" style={{display}}></div>
        <button onClick={this.changNum}>按钮</button>
        </>
      }
    }
    
    root.render(<>
      <Welcome />
    </>)
2、案例二:随机颜色
   class Welcome extends React.Component {
      state = {
        backgroundColor: "red",
      }
      // 封装随机颜色函数
      randomColor = (flag = true) => {
        // 颜色用16进制表示
        if (flag) {
          let arr = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g"]
          let str = "#"
          for (let i = 1; i <= 6; i++) {
            let random = Math.floor(Math.random() * arr.length)
            str += arr[random]
          }
          return str
        } else {
          // 颜色用rgb表示
          const r = Math.floor(Math.random() * 256)
          const g = Math.floor(Math.random() * 256)
          const b = Math.floor(Math.random() * 256)
          return `rgb(${r},${g},${b})`
        }
      }
      changNum = () => {
        let { backgroundColor } = this.state
        this.setState({
          backgroundColor: this.randomColor(true)
        })
      }
      render() {
        let { backgroundColor } = this.state
        return <>
          <div className="box" style={{ backgroundColor }}></div>
          <button onClick={this.changNum}>按钮</button>
        </>
      }
    }
    root.render(<>
      <Welcome />
    </>)

三、组件间的传值

当发生组件嵌套时,组件之间可能会共享数据,父组件会把数据传给子组件,子组件也会把数据传给父组件

1、父向子传值

在父组件中,父组件将要传递的数据写在子组件的标签上,在子组件中,通过props接收父组件传来的数据 

 //  父组件
  class Father extends React.Component{
    state={
      msg:"我是父亲,我在儿子的家里"
    }
    render(){
      return<>
      <h1>111</h1>
       <Son a={this.state.msg}/>
      </>
    }
  }
  //子组件
  class Son extends React.Component{
    render(){
      return<>
      <h1>{this.props.a}</h1>
      </>
    }
  }
    root.render(<>
      <Father/>
    </>)

2、子向父传值

子父传值通过回调函数,父组件向子组件传一个回调函数

  //  父组件
  class Father extends React.Component{
  state={
    a:"111"
  }
  // msg是子组件传的数据
  fn=(msg)=>{
    this.setState({
      a:msg
    })
  }
    render(){
      return<>
      <h1>我是父组件{this.state.a}</h1>
       <Son fn={this.fn}/>
      </>
    }
  }
  //子组件
  class Son extends React.Component{
    state={
      msg:"我修改父组件数据"
    }
    fn=()=>{
      this.props.fn(this.state.msg)
    }
   
    render(){
      return<>
      <div onClick={this.fn}>我是子组件</div>
      </>
    }
  }
    root.render(<>
      <Father/>
    </>)

3、小案例

修改个人信息案例小逻辑:我点击父组件的编辑按钮子组件显示,子组件显示的昵称与父组件相同,是父向子传值,我点击子组件的关闭按钮,子组件隐藏,这一步是子向父传值,是通过属性来控制模态框的隐藏的,在父组件中直接修改控制模态框显示隐藏的状态就可以了。点击子组件的修改按钮,父子组件的昵称都改变,这是子向父传值

 //父组件
class PersonalInfo extends React.Component{
      state={
        show:"none",
        nickname:"张三"
      }
      showModal=()=>{
        this.setState({
          show:"block"
        })
      }
      closeModal=()=>{
        this.setState({
          show:"none"
        })
      }
      updateName=(name)=>{
        this.setState({
          nickname:name
        })
      }
      render(){
        let {show,nickname}=this.state
        return <>
          <div className="container">
             <span>{nickname}</span>&nbsp;
             <button onClick={this.showModal}>编辑</button>
             <Updatemodal show={show} nickname={nickname} closeModal={this.closeModal} updateName={this.updateName}/>
            </div>
        </>
      }
    }
   
 //子组件
    class Updatemodal extends React.Component{
      state={
        name:"李四"
      }
      changeName=()=>{
        this.props.updateName(this.state.name)
      }
      render(){
        let {show,nickname,closeModal}=this.props
        return<>
         <div className="content" style={{display:show}}>
          <p>{nickname}</p>
          <button onClick={this.changeName}>修改</button>&nbsp;
          <button onClick={closeModal}>关闭</button>
          </div>
        </>
      }
    }
    root.render(<>
      <PersonalInfo/>
    </>)

四、函数组件简介

1、纯函数

纯函数:函数的返回值只依赖于它自己的参数,并且在执行的过程中没有副作用

副作用:函数或表达式在执行的过程,对外部环境产生的影响,不仅是返回一个值,副作用可能包括但不限于对全局变量,参数,数据结构,文件系统,网络请求等进行修改

不是纯函数

 let m=5
    function App(n){
     return m+n//返回值依赖了全局变量m
    }
    App(7)
    function fn(obj,a){
      obj.name="张三"
      return obj.name+a//修改了返回值参数,产生了副作用
    }
    fn({name:"李四"},6)

是纯函数

  function changeNum(a) {
      return a + 5//返回值只依赖了函数自身的变量
    }
    changeNum(9)
    function func(b) {
      let obj = { x: 1 }
      obj.x = 2
      return obj.x + b
    }
    func(6)

2、认识函数组件

在这篇文章中我们就简单的了解一下函数组件,在后面的文章中我会详细讲解在函数组件里怎么才能拥有和类组件同样的能力

函数组件其实就是js函数,它只考虑页面的渲染没有自己的状态和生命周期,没有业务逻辑代码,是一个纯函数,自从react16.8引入hooks后,函数组件就拥有了类组件的能力,如状态管理,生命周期,性能优化,复用逻辑等等

1、函数组件的书写

函数组件的组件名要大写,并返回jsx,它也有props和children,它没有this

普通函数的写法

   function Hello(props) {
      return <h1>{props.name}</h1>
    }
    root.render(<>
      <Hello name="张三" />
    </>)

箭头函数的写法

const Hello = (props) => {
      return <h1>{props.name}</h1>
    }
    root.render(<>
      <Hello name="张三" />
    </>)

2、函数组件的嵌套

<Father/>组件是<Son/>的父组件,<Son/>是<Father/>的子组件

 function Father(props) {
      return <>
        <h1>{props.name}</h1>
        <Son/>
      </>
    }
    function Son(props) {
      return <h1>我是子组件</h1>
        
    }
    root.render(<>
      <Father name="张三" />
    </>)

五、相关面试题

1、setState的更新

在面试中的回答:在React18之前,setState只在合成事件,周期函数中,批量更新,是异步更新,默认情况下,React不会对promise,setTimeout等操作中的setState进行批处理,在这些场景下,setState是同步更新

在React18中,setState在任何场景下都是异步更新的,基于updater更新队列机制,实现了状态的批处理,统一更新视图,处理流程更加稳健,提高了程序的性能

2、setState异步更新的原因

为了避免不必要的重复渲染和提高程序的性能,如果每次调用setState都同步更新状态和重新渲染组件,将会增加很多额外的开销,还可能会导致应用程序的性能下降

3、类组件与函数组件的区别

类组件:它通过继承React.Component类来定义组件,包含了自己的状态和生命周期方法,可以使用this关键字来访问组件的状态和props属性,可以使用ref来访问dom元素和子组件,可以使用setState方法来更新组件的状态,触发组件的重新渲染,通常用于复杂的组件需要管理自己的状态,并响应生命周期事件

函数组件:通过js函数来定义组件,接收props作为参数,返回jsx元素,没有自己的状态和生命周期方法,没有this,通常用于简单的展示组件,只关注UI的呈现和展示,不需要管理状态和响应生命周期事件

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐