react组件基础知识点(一)

本文深入讲解React的基础知识,包括JSX语法、组件化思想、生命周期、状态管理等关键概念,并探讨了虚拟DOM的工作原理及其带来的性能优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

React开发依赖

  • 开发React必须依赖三个库
    • react:包含react所必须的核心代码
    • react-dom:react渲染在不同平台所需要的核心代码
    • babel:将jsx转换成React代码的工具
  • 第一次接触React会被它繁琐的依赖搞蒙,对于 Vue 来说我们只依赖一个Vue.js文件,但是react居然要依赖三个库
    • cdn引入
    • 本地引入
    • npm包管理
snippet-generator.app            //代码片段生成器

认识JSX

const element = <h2>hello world</h2>
ReactDOM.render(<App />, document.getElementById('app'))
  • 这段element变量的声明右侧赋值的标签语法是什么呢?
    • 它不是一段字符串(因为没有使用引号包裹)
    • 其实是不可以的,如果我们将type="text/babel"去掉,那么就会出现语法错误
    • 它到底是什么
  • JSX是什么?
    • jsx是一种javascript 的语法扩展eXtension 也在很多地方称之为JavaScript XML, 因为看起来就是一段XML语法
    • 它用于描述我们的UI界面,并且其完成可以和javascript融合在一起使用
    • 它不同于Vue中的模板语法,你不需要专门学习模板语法中的一些指令(v-bind,v-show,v-for)

为什么选中JSX

  • react 认为渲染逻辑本质与其他UI逻辑存在内在的耦合
    • 比如UI需要绑定事件(button , a原生等等)
    • 比如UI中需要展示数据状态,在某些状态发生变化时,又需要改变UI
  • 他们之间密不可分,所以react 没有将标记分离不同的文件中,而将他们组合到一起,这个地方就是组件(Component)
  • 在这里我们只需要知道JSX是嵌入到Javascript中的一种结构语法
  • JSX的书写规范
    • JSX的顶层 只能有一个根元素,所以我们很多时候会在最外层包裹一个原生div标签
    • 为了方便阅读,我们通常在jsx的外层包裹一个小括号 这样可以方便阅读,并且jsx可以换行书写
    • jsx可以是单标签,也可以是双标签(注意:如果是单标签,必须以 /> 结尾)

JSX的使用

  • JSX嵌入变量
    • 情况1:变量是Number, String, Array类型的时候,可以正常显示
    • 情况2:当变量是null,undefined, Boolean 那么需要转成字符串
      • 如果希望可以显示null undefined Boolean 那么需要转为字符串
      • 转换的方式有很多,比如toString方法,和空字符串拼接 String(变量)等方式
    • 情况3:对象类型不能作为子元素(not valid as a React child)

JSX属性的绑定

 render() {
        const { imageUrl, warn, link, isActive } = this.state

        return (
          <div>
            {/*1.绑定普通属性*/}
            <h3 title={warn}>1.绑定普通属性</h3>
            <img src={getImgSize(imageUrl, 140)} alt={warn} />
            <a href={link} target="_blank">百度一下</a>

            {/*2.绑定class 由于jsx融合了js  因此避免js中一些关键字冲突,个别属性名会改变*/}
            <h4 className='title' >2.绑定class</h4>
            <h5 className={isActive ? 'active' : ''} onClick={this.changeColor.bind(this)}>Hello World(点击变色)</h5>
            <label htmlFor=""></label>

            {/*3.绑定style  注意此处两层大括号*/}
            <h4 style={{ color: 'red', fontSize: '50px' }} >3.绑定style</h4>
          </div>
        )
      }

JSX事件绑定

 class App extends React.Component {
      constructor() {
        super()
        this.state = {
          counter: 100
        }
        // 还可以在此处对函数方法的this进行重定向
        // this.increment = this.increment.bind(this)
      }

      render() {

        return (
          <div>
            <h2>{this.state.counter}</h2>
            {/*1.bind绑定this*/}
            <button onClick={this.increment.bind(this)}>+1</button>
            {/*2.定义函数的时候使用箭头函数*/}
            <button onClick={this.decrement}>-1</button>
            {/*3.(推荐)直接传入箭头函数,在箭头函数中调用需要执行的函数*/}
            <button onClick={() => {
              this.changeCounter('hello react')
            }}>推荐方式</button>
          </div>
        )
      }
      changeCounter(str) {
        this.setState({
          counter: this.state.counter + str
        })
      }
      increment() {
        this.setState({
          counter: this.state.counter + 1
        })
      }
      decrement = () => {
        this.setState({
          counter: this.state.counter - 1
        })
      }
    }

JSX事件参数

render() {
        return (
          <div>
            <button onClick={this.btnClick.bind()}>按钮一</button>

            <ul>
              {
                this.state.movies.map((item, index) => {
                  return (
                    <li onClick={e => { this.liClick(item, index, e) }}>
                      {item}
                    </li>
                  )
                })
              }
            </ul>
          </div>
        )
      }

      btnClick(e) {
        console.log(e);
      }

      liClick(item, index, e) {
        console.log(item, index, e);
      }
    }

React条件渲染

  • 某些情况下,界面的内容会根据不同的情况显示不同的内容,或者决定是否渲染某部分内容:
    • 在Vue中,我们会通过指令控制
    • 在React中,所有的条件判断都和普通的Javascript代码一致
  • 常见的条件渲染的方式有哪些呢?
  • 方式一:条件判断语句
    • 适合逻辑较多的情况
  • 方式二:三目运算
    • 适合真假都有值,且逻辑较少的情况
  • 方式三:逻辑运算符
    • 适合真假一边有值,且逻辑较少的情况
  • v-show 的效果
    • 主要是控制display属性是否为none

React列表渲染

class App extends React.Component {
      constructor() {
        super()
        this.state = {
          names: ['abc', 'acb', 'bac', 'bca', 'cba', 'cab'],
          numbers: [11, 22, 33, 222, 1, 33, 4523, 142, 22, 3, 132, 34, 12, 35]
        }
      }

      render() {
        return (
          <div>
            <h2>名字</h2>
            <ul>
              {
                this.state.names.map(item => {
                  return (<li>{item}</li>)
                })
              }
            </ul>
            <h2>数字</h2>
            <ul>
              {
                this.state.numbers.filter(item => item > 30).map(data => <li>{data}</li>)
              }
            </ul>
          </div>
        )
      }
    }

JSX 的本质

  • 实际上,jsx仅仅只是React.createElement(component,props,…children)函数的语法糖
    • 所有的jsx最终都会被转换成React.createElement的函数调用
  • React.createElement在源码的react文件中
  • createElement需要传递三个参数:
  • 参数一:type
    • 当前ReactElement的类型
    • 如果是标签元素,那么就使用字符串表示"div"
    • 如果是组件元素,那么就直接使用组件的名称
  • 参数二:config
    • 所有jsx中的属性都在config中以对象的属性和值的形式存储
  • 参数三:children
    • 存放在标签中的内容,以children数组的方式进行存储
    • 当然如果是多个元素呢?React内部有对他们进行处理
    • 该参数往后若多个参数,源码中是如何的实现的
      • 1.首先判断aguments -2 的长度,即除去前两个参数后还有几个参数
      • 2.如果只有一个参数,则将该参数赋给children
      • 3.不止一个参数则遍历方式将第三个参数起的参数放到一个数组中,然后与chilren参数关联

虚拟DOM的创建过程

  • 我们通过React.createElement最终创建出来一个ReactElement对象:
  • 这个ReactElement对象组成了一个JavaScript的对象树
  • javascript的对象树就是大名鼎鼎的虚拟DOM(Virtual DOM)
  • 如何查看ReactElement的树结构呢?
    • 我们可以将之前的jsx返回结果进行打印
    • 注意下面代码中我对jsx的打印
  • 而ReactElement最终形成的树结构就是Virtual DOM

为什么使用虚拟DOM

  • 为什么要采用虚拟DOM,而不是直接修改真实的DOM呢
    • 1.很难跟踪状态发生的变化:原有的开发模式,我们很难跟踪到状态发生的变化,不方便针对我们的应用程序进行调试
    • 2.操作真实的DOM性能很低:传统的开发模式会进行频繁的操作DOM
  • DOM操作性能非常低
    • 首先 document.createElement本身创建出来的就是一个非常复杂的对象
    • 其次DOM操作会引起浏览器的回流和重绘,所以开发中应该避免频繁操作DOM
虚拟DOM帮助我们呢从命令式编程转到了声明式编程的模式
  • reacr官方的说法:virtual DOM是一种编程理念
    • 在这个理念中,UI以一种理想化的方式保存在内存中,并且他是一个相对简单的javascript对象
    • 我们可以通过ReactDOM.render 让虚拟DOM和真实DOM同步起来,这个过程叫做协调(Reconciliation)
  • 这种编程的方式赋予了React声明式的API
    • 你只需要告诉React希望让UI是什么状态
    • React来确保DOM和这些状态是匹配的
    • 你不需要直接进行DOM操作,就可以从手动更改DOM。属性操作,事件处理中解放出来

React的组件化

  • 组件化思想应用
    • 有了组件化的思想,我们在之后的开发中就要充分的利用他
    • 尽可能的将页面拆成一个个小的,可复用的组件
    • 这样让我们呢的代码更加方便组织和管理,并且扩展性更强
  • React的组件相对于Vue更加灵活和多样,按照不通过的方式可以分成很多类组件
    • 根据组件的定义方式可以分为 函数组件 和 类组件
    • 根据组件内部是否有状态需要维护,可以分成:无状态组件和有状态组件
    • 根据组件的不同职责,可以分为:展示形组件和容器形组件
  • 这些概念有很多重叠,但是他们最主要是关注数据逻辑和UI展示的分离
    • 函数组件,无状态组件,展示形组件主要关注UI展示
    • 类组件,有状态组件,容器形组件主要关注数据逻辑
  • 当然还有很多其他组件的概念:比如异步组件,高阶组件等

类组件

  • 类组件的定义有如下要求:
    • 组件的名称是大写字符开头(无论类组件还是函数组件)
    • 类组件需要继承自React.Component
    • 类组件必须实现render函数
  • 在ES6之前,可已通过 create-react-class模块来定义类组件,但是目前官网建议我们使用ES6的class类定义
  • 使用class定义一个组件:
    • constructor是可选的,我们通常在constructor中初始化一些数据
    • this.state中维护的就是我们组件内部的数据
    • render()方法是class组件中唯一必须实现的方法

render函数的返回值

  • 当render被调用时,他会检查this.props和this.state的变化并返回一下类型之一
  • react元素
    • 通常通过JSX创建
    • 例如,
      会被react渲染成为DOM节点, 会被react渲染成为自定义组件
    • 以上均为react组件
  • 数组或fragments:使得渲染子节点到不同的DOM子树中
  • Portals:可以渲染子节点到不同的DOM子树中
  • 字符串或数值类型:他们在DOM中会被渲染为文本节点
  • 布尔类型或null:什么都不渲染

函数组件

  • 函数组件是使用function来进行定义的函数,只是这个函数会返回和类组件render函数一样的内容
  • 函数组件有自己的特点(当然,我们后面会将hooks,就不一样了)
    • 没有生命周期,也会被更新并挂载,但是没有生命周期函数
    • 没有this (组件实例)
    • 没有内部状态 (state)

认识生命周期

  • 很多食物都有创建到销毁的整个过程,这个过程我们称之为生命周期
  • react组件也有自己的生命周期,了解组件的生命周期让我们在最合适的地方完成自己想要的功能
  • 生命周期和证明周期函数的关系
  • 生命周期是一个抽象的概念,在生命周期的整个过程,分成了很多个阶段
    • 比如装载节点,组件第一次在DOM树中被渲染的过程
    • …类似Vue
    • componentDidMount componentDidUpdate componentWillUnmount

生命周期函数

Constructor
  • 如果不初始化state或不进行方法绑定,则不需要为React组件实现构造函数
  • constructor中通常只做两件事情
    • 通过给this.state 赋值对象来初始化内部的state
    • 为事件绑定实例this
  • componentDidMount
  • componentDidMount中通常进行哪里操作呢
    • 依赖于DOM的操作可以在这里进行
    • 在此处发送网络请求就是最好的地方
  • componentDidUpdate()会在更新后会被立即调用,首次渲染不会执行此方法
    • 当组件更新后,可以在此处对DOM进行操作
    • 如果你对更新前后的props进行了比较,也可以选择在此处进行网络请求(例如,当props未发生变化时,则不会执行网络请求)
  • componentWillUnmount
  • componentWillUnmount()会在组件卸载及销毁之前直接调用
    • 在此方法中执行必要的清理请求或清除
    • 例如 timer 取消网络请求或清除
      • 在componentDidMount()中创建的订阅等

父子组件传值(父 -> 子)

  • 父组件通过属性挂载的方式将数据挂载在元素标签的属性上

  • 子组件通过 this.props拿到挂载的这个组件上的所有属性的对象

  • 当然传递属性可以对其类型进行限制,控制,利用propTypes这个库

  • class ReactCpn1 extends Component {
      static propTypes = {
        name: propTypes.string,
        age: propTypes.number,
        height: propTypes.number
      }
    
      render () {
        const { name, age, height } = this.props
    
        return (
          <div>
            <h3>{`name:${name} === age:${age} === height:${height}`}</h3>
          </div>
        )
      }
    }
    
    // 如果是function组件的话,直接将  propTypes属性加到构造函数的属性上即可
    function ReactCpn (props) {
      const { name, age, height } = props
      return (
        <div>
          <h3>{`name:${name} === age:${age} === height:${height}`}</h3>
        </div>
      )
    }
    ReactCpn.propTypes = {
      name: propTypes.string,
      age: propTypes.number,
      height: propTypes.number
    }
    
    • defaultProps 同上可以限制父组件不给子组件传值的时候的默认值是什么
    • 视频7有props的源码分析
      • 为什么constructor中拿props为 undefined 在 componentDidMount中却能拿到
      • render()中也能拿到

子传父

  • 思路一样是父组件中使用子组件,给子组件绑定属性,属性对应方法的调用
  • 子组件拿到该属性对象的方法
  • 将该方法绑定到特定事件上或者其他方式进行执行即可
  • 数据以参数形式进行传递

react实现slot插槽

  • 两种方案,一种是个插槽中只有一个元素,另一种适合多个元素
  • 一个元素的时候,则无顺序,使用双标签
    • 直接在组件双标签中写入插入的内容元素,或组件即可
    • 在子组件中通过this.props.children拿到插入元素的数组集合
    • 通过索引拿到 插入到子组件中即可
  • 多个元素的时候,有顺序,建议单标签
    • 通过传值的形式将元素,组件通过props传到子组件
    • 子组件通过 this.props 解构拿到插入的元素
    • 将这些元素放入子组件中对应的位置即可

跨组件传值

  • 两套方案:1.一层层传递 2.利用context

    • 一层层传递的时候可以使用 属性展开的方式 {…props}

    • context方式

      • 1.创建Context对象

        const UserContext = React.createContext({
        	nickName:'abc',
        	level:-1
        })
        
      • 2.通过 UserContext.Provider 的组件包裹的方式,包裹住子孙组件,并给UserContext.Provider组件绑定 value值,绑定的对象就是传递的数据

      • 3.通过 目标组件.contextType = UserContext 方式绑定

      • 4.之后再目标组件中通过 this.context拿到传递过来的数据

    • 上面context方式目标组件需要是类组件,当是函数式组件的时候

      • 1.return一个UserContext.Consumer包裹的组件
      • 2.UserContext.Consumer包裹的jsx通过 value=>{ }函数的形式返回
      • 3.value参数就是上层传递下来的数据

为什么使用setState

  • 开发中我们并不能直接通过修改state的值来让界面发生更新
    • 因为我们修改了state之后,希望React根据最新的State来重新渲染界面,但是这种方式的修改并不能让React知道
    • React并没有实现类似Vue2中的Object.defineProperty或者Vue3的Proxy方式来监听数据 变化
    • 我们必须通过setState来告知React数据已经发生了变化
  • 疑惑:在组件中并没有实现setState方法,为什么可以调用
    • 原因很简单,setState方法是从Component中继承过来的

setState异步更新

  • setState的更新是异步的?
    • 最终打印结果是Hello world
    • 可见setState是异步的操作,我们并不能在执行完setState后立马拿到最新的state结果
  • 为什么setState涉及为异步?
    • setState涉及为异步其实在GITHUB上也有很多讨论
    • 之后react核心成员 redux的作者进行了回复
  • 简单总结该回复
    • setState涉及为异步,可以显著提高性能
      • 如果每次调用setState都进行一次更新,那么意味着render函数会被频繁的调用,界面重新渲染,效率很低
      • 最好的办法应该是获取多个更新,之后进行批量更新
    • 如果更新了state,但是render函数还没有执行,那么state和props没有能够保持同步
      • state和 props不能保持一致性,会在开发中产生很多的问题

如何获取异步的结果

  • 方式一 setState回调
    • setState 接受两个参数:第一个参数是回调函数,这个回调函数会在更新后执行
    • 格式如下: setState( partialState, callback )
  • 当然也可以在ComponentDidUpdate生命周期中执行,此时state也会是更新后的状态

setState一定是异步的吗

不是

  • 如果在setTimeout中的更新,则会同步
  • 如果在原生DOM事件监听中执行,也会是同步
  • 其实分成了以下两个情况:
    • 在组件中生命周期或React合成事件中,setState是异步
    • 在setTimeout或者原生dom事件中setState是同步

数据的合并

  • 通过setState去修改message 是不会对name产生影响的(用setState对state中某个数据进行更新,不会影响其他数据)
    • 因为源码其实是有对原对象和新对象进行合并的,通过 Object.assign({ }, preState,partialState)方法

多个state的合并

  • 如果setState异步方式进行更新操作,并且更新同样的内容多次,则最终并不能导致state中数据被进行了三次同样的操作

    • 因为react内部会对这些更新操作先放到一个队列中,最终进行合并更新(个人理解多次操作,在拿到this.state.xx数据的时候,此时多次拿到的这个数据都是一样的,因此,异步操作后,数据依旧是只从初始数据改变了一次)
  • 如果想要多次同样的数据操作能够不被合并,原本传入setState中的对象应该改为传入一个函数,返回一个对象

    • this.setState((state,props) => {
          return {
              counter:state.counter + 1
          }
      })
      

React更新机制

  • JSX —> 虚拟DOM -----> 真实DOM
  • react的更新流程
    • props/state改变 -----> render函数重新执行 -----> 产生新的DOM树 -----> 新旧DOM树进行diff算法比较 -----> 计算出差异 -----> 更新到真实的DOM
  • react的diff算法是 优化过的,或者说是简化国的
    • 同层节点之间相互比较,不会跨节点比较
    • 不同了类型的节点,产生不同树结构
    • 开发中,可以通过KEY来指定哪些节点在不同的渲染下保持稳定
  • 不同类型的元素,删除,添加,更改都会对节点进行渲染
  • 单纯的该className style等元素不会重新渲染更新

KEY值的优化

  • 首先默然情况下,react对子节点的渲染更新是通过递归比较的方式进行的
  • 更新后的树节点和之前的树节点会一层层的比较,当有一层不同时,则会生成一个mutation,之后将产生mutation的节点进行重新渲染更新
    • 由于上面的机制,当我们在这个树节点中插入一个元素,则会导致该元素开始的之后的所有节点都会生成一个mutation 会导致原本后面没有更改的节点全部重新渲染更新,这样的会造成性能问题
  • 因此我们需要通过一个标记来标记这些个节点,这就引出了我们的key
    • 当树节点都绑定了key值的话,这个时候插入一个节点,此时diff算法比较的时候,其内部就能知道插入的元素是新插入的元素,该元素后面的节点只需要让出位置给这个新插入的节点即可,在节点的比较的时候,也会根据对应的key来比较节点,这样就不会生成那么多mutation 更新那么多原本不必要更新的节点了

render函数被调用

  • 按照之前的代码开发,当我们修改根组件中的state中的数据的时候,会导致根组件,包括子孙组件的render函数被调用,这样大大的降低了性能
  • 如何控制render是否被调用,可以通过 shouldComponentUpdate 方法即可(简称SCU)
    • 该方法有两个参数:参数1:nextProps 修改之后,最新的props属性,参数2:nextState修改之后,最新的state属性
    • 该方法返回值是一个boolean类型
      • 返回值true,那么久需要调用render方法
      • 返回值false,那么就不需要调用render方法
      • 默认是true,也就是state变化,就会调用render

PureComponent

  • 如果所有的类,我们都需要手动来实现shouldCompinentUpdate,那么会增加我们很多的工作量
  • react已经考虑到了这点,因此我们可以将class继承自PureComponent
  • 源码中这个组件首先是默认返回isPureReactComponent 为 true的,其次先判断了 shouldUpdate是否等于undefined, 之后判断 isPureReactComponent 是否为true,是的话然后通过 shallowEqual方法 对新旧 state进行浅层比较 ,如果有变化则更新, 没有则不更新

高阶组件memo

  • PureComponent的方式是针对 类组件的 ,那么函数式组件该如果处理
    • 我们将之前的Header、Banner、ProductList都通过memo函数进行一层包裹
    • Footer没有使用memo函数进行包裹;
    • 最终的效果是,当counter发生改变时,Header、Banner、ProductList的函数不会重新执行,而Footer的函数会被重新执 行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值