React重点知识拓展,含Hooks、路由懒加载等

第7章:React扩展

一、setState

1.setState更新状态的2种写法

  • setState(stateChange, [callback])------对象式的setState

    • stateChange为状态改变的对象(该对象可以体现出状态的更改)
    • callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
  • setState(updater, [callback])------函数式的setState

    • updater为一个函数,返回stateChange对象。
    • updater可以接收到state和props
    • callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。

2. 总结

  • 对象式的setState是函数式的setState的简写方式(语法糖)

  • 使用原则:

    • 如果新状态不依赖于原状态 ===> 使用对象方式
    • 如果新状态依赖于原状态 ===> 使用函数方式
    • 如果需要在setState()执行后获取最新的状态数据, 要在第二个callback函数中读取
  • setState更新状态的动作是异步的

    我们会考虑在 setState 更新之后 log 一下

    add = () => {
     const { count } = this.state
     this.setState({
         count: count + 1
     })
     console.log(this.state.count);
    }
    

    因此可能会写出这样的代码,看起来很合理,在调用完 setState 之后,输出 count

    我们发现显示的 count 和我们控制台输出的 count 值是不一样的

    这是因为,我们调用的 setState 是同步事件,但是它的作用是让 React 去更新数据,而 React 不会立即的去更新数据,这是一个异步的任务,因此我们输出的 count 值会是状态更新之前的数据。“React 状态更新是异步的

二、lazyLoad

1. 路由组件的lazyLoad

懒加载在 React 中用的最多的就是路由组件了,页面刷新时,所有的组件都会重新加载,这并不是我们想要的,我们想要实现点击哪个路由链接再加载即可,这样避免了不必要的加载。若不使用路由懒加载技术,我们页面一加载时,所有的路由组件都会被加载。

  • 首先我们需要从 react 库中暴露一个 lazy 函数

    通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包

    import React, { Component ,lazy} from 'react';

  • 然后我们需要更改引入组件的方式

    // import Home from "./Home";
    // import About from "./About";
    const Home = lazy(() => import("./Home"));
    const About = lazy(() => import("./About"));
    
  • 使用Suspense标签包括要显示的路由组件(Route标签),当我们网速慢的时候,路由组件就会有可能加载不出来,页面就会白屏,它需要我们使用fallback属性来指定一个路由组件加载的东西,相对于 loading

    <Suspense fallback={<h1>loading</h1>}>
        <Route path="/home" component={Home}></Route>
        <Route path="/about" component={About}></Route>
    </Suspense>
    
  • 注意:因为 loading 是作为一个兜底的存在,因此 loading 是 必须提前引入的,不能懒加载

三、Hooks

React Hook/Hooks是什么?

  • Hook是React 16.8.0版本增加的新特性/新语法
  • 可以让你在函数组件中使用 state 以及其他的 React 特性

三个常用的Hook

  • State Hook: React.useState()
  • Effect Hook: React.useEffect()
  • Ref Hook: React.useRef()

1. State Hook

State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作

  • 首先我们需要明确一点,由于函数处于严格模式,函数式组件没有自己的 this

  • 语法: const [xxx, setXxx] = React.useState(initValue)

    function Demo() {
        const [count, setCount] = React.useState(0)
        console.log(count, setCount);
        function add() {
            setCount(count + 1)
        }
        return (
            <div>
                <h2>当前求和为:{count}</h2>
                <button onClick={add}>点我加1</button>
            </div>
        )
    }
    export default Demo
    
  • useState()说明:它让函数式组件能够维护自己的 state

    • 接收一个参数,作为初始化 state 的值,useState 的初始值只有第一次有效
    • useState 能够返回一个数组,第一个元素是 state ,第二个是更新 state 的函数
    • 更新状态函数setXxx的两种写法
      • setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
      • setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值

2. Effect Hook

在类式组件中,提供了一些声明周期钩子给我们使用,我们可以在组件的特殊时期执行特定的事情,例如 componentDidMount ,能够在组件挂载完成后执行一些东西

在函数式组件中也可以实现,它采用的是 effectHook ,它的语法更加的简单,同时融合了 componentDidMountcomponentDidUpdatacomponentWillUnmount生命周期,极大的方便了我们的开发

  • Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)

  • React中的副作用操作:

    • 发ajax请求数据获取
    • 设置订阅 / 启动定时器
    • 手动更改真实DOM
  • (3). 语法和说明:

    useEffect(() => { 
        // 在此可以执行任何带副作用操作
        return () => { // 在组件卸载前执行
            // 在此做一些收尾工作, 比如清除定时器/取消订阅等
        }}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
    
  • 可以把 useEffect Hook 看做如下三个函数的组合

    • componentDidMount()
    • componentDidUpdate()
  • React.useEffect说明

    • 第一个参数是一个回调函数,第二个参数是一个数组
    • 第二个参数不写所有state更新时,执行回调函数
    • 添加第二个参数(数组):数组中对应的state更新时,执行回调函数,和Vue中的watch侦听类似
    • 若为空数组[],相当于componentDidMount,回调函数只会在第一次render()后执行
    • 返回值是一个函数,该函数会在组件被卸载时执行,相当于componentWillUnmount()
    React.useEffect(() => {
      let timer = setInterval(() => {
        setCount((count) => count + 1);
      }, 1000);
    
      // 返回componentWillUnmount
      return () => {
        clearInterval(timer);
      };
    }, []);
    

3. Ref Hook

当我们想要获取标签内的信息时,在类式组件中,我们会采用 ref 的方式来获取。在函数式组件中,我们可以采用也可以采用 ref 但是,我们需要采用 useRef 函数来创建一个 ref 容器,这和 createRef 很类似。

  • 使用useRef函数创建ref容器

    const myRef = useRef()

  • 给要获取的结点打ref标签

    <input type="text" ref={myRef} />

  • 获取input框输入的值

    function show() {
        alert(myRef.current.value)
    }
    
  • 重点是:useRef函数的使用

    • 语法: const refContainer = useRef()
    • 作用:保存标签对象,功能与React.createRef()一样

四、Fragment

  • 我们编写组件的时候每次都需要采用一个 div 标签包裹才能让它正常的编译,但是这样会引发什么问题呢?
    在这里插入图片描述
    它包裹了几层无意义的 div 标签,我们可以采用 Fragment 来解决这个问题

  • 我们需要从 react 中暴露出 Fragment ,将我们所要去掉的div采用 Fragment 标签进行替换,当它解析到 Fragment 标签的时候,就会把Fragment去掉

  • 同时采用空标签,也能实现,但是它不能接收任何值,而 Fragment 能够接收唯一一个 属性key

  • 使用:<Fragment key={id}><Fragment><></>

  • 作用:可以不用必须有一个真实的DOM根标签了

五、Context

一种组件间通信方式,常用于【祖组件】与【后代组件】间通信

1. 仅适用于类式组件

  • 首先我们需要全局创建一个 MyContext 对象,我们需要引用MyContext 下的 Provider

    // 创建Context容器对象
    const MyContext = React.createContext();
    const { Provider } = MyContext;
    
  • 渲染子组件时,外面包裹Provider, 通过value属性给后代组件传递数据

    <Provider value={{ username, age }}>
        <B />
    </Provider>
    
  • 在需要使用数据的后代组件,引入MyContext

    // 声明context
    static contextType = MyContext;
    
  • 在使用数据时,直接从 this.context 上取值即可

    const {username,age} = this.context
    

2. 适用于函数式和类式组件

  • 首先我们需要全局创建一个 MyContext 对象,我们需要引用MyContext 下的 ProviderConsumer

    // 创建Context容器对象
    const MyContext = React.createContext();
    const { Provider , Consumer } = MyContext;
    
  • 渲染子组件时,还是一样,外面包裹Provider, 通过value属性给后代组件传递数据

  • 在需要使用数据的子组件中,使用<Consumer></Consumer>包裹,通过 value 取值即可

    function C() {
      return (
        <div>
          <h3>我是C组件,我从A接收到的数据 </h3>
          <Consumer>
            {(value) => {
              return `${value.username},年龄是${value.age}`;
            }}
          </Consumer>
        </div>
      );
    }
    

3. 注意

​ 在应用开发中一般不用context, 一般都它的封装react插件

六、组件优化

1. Component的2个问题

  • 只要执行setState(),即使不改变状态数据, 组件也会重新render()

  • 只当前组件重新render(), 就会自动重新render子组件 ==> 效率低

2. 效率高的做法

  • 只有当组件的state或props数据发生改变时才重新render()

3. 原因

  • Component中的shouldComponentUpdate()总是返回true

4. 解决

  • 办法1:

    • 重写shouldComponentUpdate()方法​

    • 比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false

    • 如下代码

      shouldComponentUpdate(nextProps, nextState) {
          // console.log(this.props, this.state); // 目前的props和state
          // console.log(nextProps, nextState); // 接下来要变化的目标props,目标state
          return !(this.state.carName === nextState.carName);
      }
      
  • 办法2:

    • 使用PureComponent

      react 身上暴露出 PureComponent 而不使用 Component

      import React, { PureComponent } from 'react'
      
    • PureComponent重写了shouldComponentUpdate(),只有state或props数据有变化才返回true

    • 注意:

      • 只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false

        • 错误示例:

          // stus为数组
          stus.unshift("小刘");
          this.setState({ stus });
          
          const obj = this.state;
          obj.carName = "迈巴赫";
          console.log(obj === this.state); // true,判断时认为数据未变化,不更新页面
          this.setState(obj);
          
      • 不要直接修改state数据, 而是要产生新数据

    • 项目中一般使用PureComponent来优化

七、render props

采用 render props 技术,我们可以像组件内部动态传入带有内容的结构,和Vue插槽类似

1. 组件标签内填入内容时

  • 当我们在一个组件标签中填写内容时,这个内容会被定义为 children props,我们可以通过 this.props.children 来获取

  • 如:

    <A>hello</A>
    
  • 这个 hello 我们就可以通过 children 来获取

2. 如何向组件内部动态传入带内容的结构(标签)?

  • Vue中:
    • 使用slot技术, 也就是通过组件标签体传入结构 <AA><BB/></AA>
  • React中:
    • 使用children props: 通过组件标签体传入结构​
    • 使用render props: 通过组件标签属性传入结构, 一般用render函数属性

3. children props

  • 在Parent组件中

    <A>
    	<B>xxxx</B>
    </A>
    
  • A组件中使用B组件,AB组件为父子组件关系

    {this.props.children}

  • 问题: 如果B组件需要A组件内的数据 ==> 做不到

4. render props

  • 在Parent组件中

    <A render={(name) => <B name={name} />} />

  • A组件: {this.props.render(name)}

  • C组件: 读取A组件传入的数据显示{this.props.name}


八、错误边界

1. 理解:

错误边界:用来捕获后代组件错误,渲染出备用页面

2. 特点:

只能捕获后代组件生命周期产生的错误不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误

3. 使用方式:

getDerivedStateFromError配合componentDidCatch

// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
    console.log(error);
    // 在render之前触发
    // 返回新的state
    return {
        hasError: true,
    };
}

componentDidCatch(error, info) {
    // 统计页面的错误。发送请求发送到后台去
    console.log(error, info);
}

九、组件通信方式总结

1. 方式:

  • props:

    • children props

    • render props​

  • 消息订阅-发布:pubs-sub、event等等​

  • 集中式管理:redux、dva等等

  • Context:生产者-消费者模式

2. 选择方式

  • 父子组件:props
  • 兄弟组件(非嵌套组件):消息订阅-发布、集中式管理
  • 祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(用的少)
posted @   你就是星光  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示