react中类组件以及常见hooks总结

类组件:所谓类组件,就是基于 ES6 Class 这种写法,通过继承 React.Component 得来的 React 组件。

React 类组件内部预置了相当多的“现成的东西”等着我们去调度/定制,state 和生命周期就是这些“现成东西”中的典型。要想得到这些东西,难度也不大,只需要继承一个 React.Component 即可。

当然,这也是类组件的一个不便,它太繁杂了,对于解决许多问题来说,编写一个类组件实在是一个过于复杂的姿势。复杂的姿势必然带来高昂的理解成本,这也是我们所不想看到的。除此之外,由于开发者编写的逻辑在封装后是和组件粘在一起的,这就使得类组件内部的逻辑难以实现拆分和复用

函数组件:函数组件就是以函数的形态存在的 React 组件。早期并没有 React-Hooks,函数组件内部无法定义和维护 state,因此它还有一个别名叫“无状态组件”。

相比于类组件,函数组件肉眼可见的特质自然包括轻量、灵活、易于组织和维护、较低的学习成本等。

通过对比,从形态上可以对两种组件做区分,它们之间的区别如下:

  • 类组件需要继承 class,函数组件不需要;
  • 类组件可以访问生命周期方法,函数组件不能;
  • 类组件中可以获取到实例化后的 this,并基于这个 this 做各种各样的事情,而函数组件不可以;
  • 类组件中可以定义并维护 state(状态),而函数组件不可以;

除此之外,还有一些其他的不同。通过上面的区别,我们不能说谁好谁坏,它们各有自己的优势。在 React-Hooks 出现之前,类组件的能力边界明显强于函数组件。

实际上,类组件和函数组件之间,是面向对象和函数式编程这两套不同的设计思想之间的差异。而函数组件更加契合 React 框架的设计理念:
React 组件本身的定位就是函数,一个输入数据、输出 UI 的函数。作为开发者,我们编写的是声明式的代码,而 React 框架的主要工作,就是及时地把声明式的代码转换为命令式的 DOM 操作,把数据层面的描述映射到用户可见的 UI 变化中去。这就意味着从原则上来讲,React 的数据应该总是紧紧地和渲染绑定在一起的,而类组件做不到这一点。
函数组件就真正地将数据和渲染绑定到了一起。函数组件是一个更加匹配其设计理念、也更有利于逻辑拆分与重用的组件表达形式。
为了能让开发者更好的的去编写函数式组件。于是,React-Hooks 便应运而生。
React-Hooks 是一套能够使函数组件更强大、更灵活的“钩子”。
函数组件比起类组件少了很多东西,比如生命周期、对 state 的管理等。这就给函数组件的使用带来了非常多的局限性,导致我们并不能使用函数这种形式,写出一个真正的全功能的组件。而React-Hooks 的出现,就是为了帮助函数组件补齐这些(相对于类组件来说)缺失的能力。

Hook 的优势

  • Hook 使你在无需改变组件结构的情况下复用状态逻辑(自定义 Hook)
  • Hook 将组件中互相关联的部分拆分成更小的函数(比如设置订阅或请求数据)
  • Hook 使你在非 class 的情况下可以使用更多的 React 特性

Hook 使用规则

Hook 就是 Javascript 函数,使用它们时有两个额外的规则:

  • 只能在函数外层调用 Hook,不要在循环、条件判断或者子函数中调用
  • 只能在 React 的函数组件自定义 Hook 中调用 Hook。不要在其他 JavaScript 函数中调用

在组件中 React 是通过判断 Hook 调用的顺序来判断某个 state 对应的 useState的,所以必须保证 Hook 的调用顺序在多次渲染之间保持一致,React 才能正确地将内部 state 和对应的 Hook 进行关联。

1.useState

useState用于在函数组件中调用给组件添加一些内部状态 state,正常情况下纯函数不能存在状态副作用,通过调用该 Hook 函数可以给函数组件注入状态 state

useState 唯一的参数就是初始 state,会返回当前状态和一个状态更新函数,并且 useState 返回的状态更新函数不会把新的 state 和旧的 state 进行合并,如需合并可使用 ES6 的对象结构语法进行手动合并

类似于类组件中的this.state状态管理

可以让数据扁平化

方便解构

函数组件也可以使用状态

const [变量,修改状态变量的方法] = useState(初始值|()=>初始值)

// 在react16.8之前,函数组件是没有状态的,在react16.8之后所有hooks新特性,有了状态
// useState 可以让react函数组件有状态
// useState语法
// const [变量,修改状态变量的方法] = useState(初始值|()=>初始值)
// hooks它就是一个工具函数,所以在一个函数组件中可以被调用N次
----
//count用来获取100,setCount用来实现对count的操作
const [count, setCount] = useState(100)
return (
  <div>
    <h3>useState:{count}</h3>
    <button
      onClick={() => {
        //修改count的方法,在此方法中使得count+1
        setCount(count + 1)
      }}
    >
      ++count++
    </button>
  </div>
)

在使用useState过程中要注意闭包陷阱

// 闭包陷阱,count的结果不会一直加一
 setInterval(() => {
   console.log(1)
   setCount(count + 1)
 }, 1000)

// 解决 -- 让更新状态的方法使用回调函数的方案来进行,这样就可以很好的去解决闭包问题
// 如果你是覆盖原值,你可以直接给值,如果你是在原数据的基础上去修改值,建议用回调函数
setInterval(() => {
  // v 就是回调中传过来的最新的count的值[状态]
  setCount((v) => v + 1);
}, 1000);

闭包作用:1)保护:让变量不受外界干扰,防止变量污染 2)保存:将变量保存来,变量持久化

2.useEffect

副作用处理函数,函数组件没有生命周期,此函数可以模拟重要生命周期(主要),起到监视器作用,监听状态值的改变(次要)

可以模拟以下生命周期

componentDidMount(网络请求)componentDidUpdate componentWillUnmount(销毁不必要的数据,如计时器、定时器)

注:useEffect中的回调函数返回值只能是undefind或函数,别的都不行,因为无法释放

语法:

useEffect(()=>{
  return () => {}
},[依赖项])
// 1.componentDidMount componentDidUpdate ---两者同时模拟
 useEffect(()=>{})
// 2.componentDidMount ---只模拟componentDidMount
 useEffect(()=>{},[])
// 3.componentDidMount componentDidUpdate componentWillUnmount ---三者同时模拟
 useEffect(()=>{return ()=>{Willunmount}})
// 4.componentDidMount componentWillUnmount
 useEffect(()=>{return ()=>{}},[])

注:如果useEffect它的第2个参数不为空,也不为空数组,它数组中的变量就是它的依赖项,只要依赖项发生改变,则它会就重新触发
当有依赖项时
有依赖  --- 此功能有点类似于vue中的watch
useEffect(() => {
  console.log('count有变化')
}, [count])

useEffect(() => {
  console.log('count/username有变化')
}, [count, username])

useEffect(() => {
  // 修改后
  console.log('after', count)
  return () => {
    // 此处就是依赖项修改之前你要去处理的工作
    console.log('before', count)
  }
}, [count])
useEffect可以模拟以下生命周期
1.挂载时模拟componentDidMount
useEffect(()=>{},[])
用法:
一般会在此处进行网络请求
useEffect(() => {
  console.log('componentDidMount')
  // document.querySelector('h3').innerHTML = 'hahahahah'
}, [])
2.挂载和更新模拟componentDidMount componentDidUpdate
注:此写法中,切记不能进行state数据更新,否则死循环
useEffect(() => {
  console.log('componentDidMount componentDidUpdate')
})
只有在更新时和挂载时会触发
3.挂载、更新、销毁时模拟componentDidMount componentDidUpdate componentWillUnmount
数据更新时它触发 componentDidUpdate / componentWillUnmount
如果有路由,切换时会触发 componentWillUnmount
useEffect(() => {
  console.log('componentDidMount componentDidUpdate')
  return () => {
    console.log('componentWillUnmount')
  }
})
4.挂载、销毁时模拟componentDidMount componentWillUnmount
只有页面销毁才能执行到 componentWillUnmount
useEffect(() => {
  console.log('componentDidMount')
  return () => {
    console.log('componentWillUnmount')
  }
}, [])

3.useLayoutEffect

在浏览器绘制屏幕前触发,触发时机早于useEffect,可以理解为beforeMount,useEffect理解为mount,用法和useEffect完全相同的。可能会影响性能,尽量少使用。

useEffect和useLayoutEffect两者区别:

在 React 中,useEffectuseLayoutEffect 是用于处理副作用(side effects)的 Hook。它们非常相似,但有一些关键的区别。

  1. 执行时机不同:
    • useEffect 的副作用函数是在浏览器绘制完成后异步执行的,即在组件渲染和布局完成后执行,不会阻塞页面的渲染。

    • useLayoutEffect 的副作用函数是在浏览器绘制之前同步执行的,即在组件渲染和布局之后、浏览器绘制之前执行,在这个阶段可以直接操作 DOM,但也因此可能会阻塞页面的渲染。

  2. 使用场景不同:
    • 通常情况下,我们应该优先考虑使用 useEffectuseEffect 在大多数情况下都能满足我们的需求,并且它能保证不阻塞页面渲染,适用于大部分副作用的处理,如数据获取、订阅和取消订阅等。

    • 在某些特定场景下,需要在 DOM 更新之后立即执行某些操作,例如测量 DOM 元素的尺寸、同步更新其他的 DOM 元素等。这时可以使用 useLayoutEffect 来确保副作用函数在浏览器绘制前立即执行。

总结:
  • 如果副作用不需要立即执行或涉及 DOM 操作较少,推荐使用 useEffect

  • 如果副作用需要在 DOM 更新之后立即执行或涉及 DOM 操作较多,推荐使用 useLayoutEffect

需要注意的是,无论是 useEffect 还是 useLayoutEffect,都要避免在副作用函数中进行频繁的更新操作,以免陷入无限循环的情况。

4.useReducer

给组件添加状态,写法和redux类似,无法使用redux提供的中间件

// useReducer它的功能和useState是一样的,给组件添加状态
// useReducer和useState的区别是:
// useState是基于状态的,而useReducer是基于动作的 [操作和redux是一样的,状态和操作是分离的]
// useReducer它主要运用于一些较复杂的场景,还可以实现组件的跨层级通信
// 语法
// 参数1:操作state的纯函数
// 参数2:给state的初始值,它有值,则不用设置参数3
// 参数3:回调函数,返回一个对象,惰性赋值,用到时才进行初始化,性能更好一些,如果有参数3,则参数2的初始化就无效,可以设置为null
// 参数2和参数3,两者用一个就可以
// useReducer(reducer,initState,[()=>({})])

useReducer使用

// useReducer中的数据
const initState = {
  count: 100
}
// 注:此处的state不用给默认值,在useReducer中完成了初始
//reducer中的state是上一次的state,若是第一次,则是初始状态,理解这点很重要
const reducer = (state, { type, payload }) => {
  if ('incr' === type) return { ...state, count: state.count + payload }
  if ('decr' === type) return { ...state, count: state.count - payload }
  return state
}

const App = () => {
  // const [{ count }, dispatch] = useReducer(reducer, initState)
  // 惰性初始化 性能更高一些
  const [{ count }, dispatch] = useReducer(reducer, null, () => initState)

  return (
    <div>
      <h3>{count}</h3>
      <button
        onClick={() => {
          dispatch({ type: 'incr', payload: 1 })
        }}
      >
        ++++
      </button>
      <button
        onClick={() => {
          dispatch({ type: 'decr', payload: 1 })
        }}
      >
        ----
      </button>
    </div>
  )
}

5.useContext

Context 提供了一个无需为每层组件手动添加 props ,就能在组件树间进行数据传递的方法,useContext 用于函数组件中订阅上层 context 的变更,可以获取上层 context 传递的 value prop 值

useContext 接收一个 context 对象(React.createContext的返回值)并返回 context 的当前值,当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider>value prop 决定

// useContext它是提供给函数组件来消费Context数据所用
import { useState, useContext } from 'react'
// useContext它是提供给函数组件来消费Context数据所用
// import ctx from '@/context/appContext'
// import ctx, { Provider, Consumer } from '@/context/appContext'
import {createContext} from 'react'
const ctx = createContext()

//在父组件中拿到ctx中解构出Peovider,并将store的数据塞到useContext中
const App = () => {
  const { Provider } = ctx
  const [name, changeName] = useState("liqi111")
  const [count, setCount] = useState(100)
  const store = { count, setCount, name, changeName }

  return (
    <div>
      <Provider value={store}>
        <Child1 />
        <Child2 />
        <Child3 />
      </Provider>
    </div>
  )
}


//在子组件中通过解构得到useContext中的数值和方法,并将数值显示,方法操作
const Child1 = () => {
  const { count } = useContext(ctx)
  return (
    <div>
      <h3>{count}</h3>
    </div>
  )
}

const Child2 = () => {
  const { setCount, changeName } = useContext(ctx)
  const modifyCount = () => {
    setCount(v => v + 1);
  }
  const setName = () => {
    changeName(v => v === "liqi111" ? "liqi222" : "liqi111")
  }
  return (
    <div>
      <button onClick={() => modifyCount()}>++++++</button>
      <button onClick={() => setName()}>改名</button>
    </div>
  )
}

const Child3 = () => {
  const { name } = useContext(ctx)
  return (
    <div>
      <h3>{name}</h3>
    </div>
  );
}

6.useMemo

useMemo它是一个性能hook

useMemo它就是一个缓存组件[计算属性],它是用来缓存值的

提升性能的,但如果你的计算结果不是多次去调用,也没有必要去使用,使用它也会有性能消耗

const App = () => {
  const [num1, setNum1] = useState(1)
  const [num2, setNum2] = useState(2)

  // 缓存计算后的值,如果它的依赖项没有发生改变,则下一次调用时,读取缓存结果,提升性能
  // 依赖项,都只是做浅层对比
  const sum = useMemo(() => {
    console.log('sum')
    return num1 + num2
    //同时监听num1,num2,若两者其中之一发生改变,则会打印sum
  }, [num1, num2])
  //只监听num2,此时修改num1,不会打印sum
  //}, [num2])


  return (
    <div>
      num1:
      <input
        type="number"
        value={num1}
        onChange={e => setNum1(v => v + e.target.value * 1)}
      />
      <hr />
      <div>{sum}</div>
      <div>{sum}</div>
      <div>{sum}</div>
      <div>{sum}</div>
      <div>{sum}</div>
      <div>{sum}</div>
      <div>{sum}</div>
    </div>
  )
}

7.useCallback

useCallback它是一个性能hook

useCallback它是把操作方法给缓存起来,只要它的依赖项没有发生改变,则它在调用函数时会走缓存,不会重复创建

建议useCallback使用的场景为,父中的方法传递给子组件中去使用时,才用

//子组件
const Child = ({ addNum }) => {
  useEffect(() => {
    console.log(1)
    //监视依赖项addNum,若addNum有变化则打印1
  }, [addNum])

  return (
    <div>
      <button onClick={addNum}>+++++</button>
    </div>
  )
}

const App = () => {
  const [num, setNum] = useState(1)
  const [a] = useState(100)

  // 此写法,它会在组件更新时,重新创建一次addNum,若传给子组件则会触发useEffect
  // const addNum = () => {
  //   setNum(v => v + 1)
  // }

  // 缓存起来,只要依赖项没有发生改变
  // 参数2:如果为一个空数组,表示它没有任何依赖,则此函数只会创建1次
  // const addNum = useCallback(() => {
  //   setNum(v => v + 1)
  // }, [])

  //这样操作还是只会创建一次addNum,因为依赖项为a,但是a在useState中已经给了初值并且不会变动
  //相当于依赖项没有过变化,因此还是只会创建一次
  const addNum = useCallback(() => {
    setNum(v => v + 1 + a)
  }, [a])

  return (
    <div>
      <h3>{num}</h3>
      <Child addNum={addNum} />
    </div>
  )
}

8.useRef

useRef 用于返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue

useRef 创建的 ref 对象就是一个普通的 JavaScript 对象,而 useRef() 和自建一个 {current: ...} 对象的唯一区别是,useRef 会在每次渲染时返回同一个 ref 对象

// createRef和useRef区别

// 1.createRef可以用在类组件和函数组件中,useRef只能用在函数组件中

// 2.createRef不能设置初始值,而useRef可以设置初始值,不设置初始值的时候,这默认值为null

// 3.createRef在函数组件更新时会重新创建,而useRef只会在初始化时执行1次,后续不会重新创建

const elCreateRef = createRef()//不能设置初值
const elUseRef = useRef(100)//可以设置初值

//监视count的变化
useEffect(() => {
     //这里每次都打印null,因为每次都要重新创建
    console.log('createRef', elCreateRef.current);
     //这里打印的值会一次加一,因为他只创建一次,所以组件不更新他不会重置
    console.log('useRef', elUseRef.current);
  }, [count])

//修改对比的ref值,count的变化只是用来引起监视器,从而打印上面两句代码
<button
  onClick={() => {
   //由于crateRef每次都会重新创建,所以打印出来的每次都是1
    elCreateRef.current = elCreateRef.current ? elCreateRef.current : 1
    console.log(elCreateRef.current)
    setCount(v => v + 1)
  }}
>
  createRef
</button>
<button
  onClick={() => {
    elUseRef.current++
    setCount(v => v + 1)
  }}
>
  useRef
</button>

useRef的使用场景:

1)用于创建一个可变的引用。
2)利用 useRef 实现组件间的通信 
  1. 传递数据给子组件 :在组件层次结构中传递数据给子组件,以避免数据的多次传递和组件的嵌套。
  2. 保存全局状态 :在多个组件中共享同一变量,以避免状态丢失和混乱。
3)利用 useRef 实现定时器等操作 

9.自定义hooks

react中自定义hooks必须满足如下条件

// 1.它必须是以use开头的函数

// 2.它内部要使用到内置的hooks函数

import { useState } from 'react'
//将value和setValue抽离出来,仅通过接受参数,返回对象的方式来简化代码*****多学
const useInput = (initValue = '') => {
  const [value, setValue] = useState(initValue)

  return {
    value,
    onChange: e => setValue(e.target.value)
  }
}

export default useInput

---
//使用
  
  const username = useInput('')
  const password = useInput('')

  return (
    <div>
      <div>
        账号:
        <input {...username} />
      </div>
      <div>
        密码:
        <input {...password} />
      </div>
      <div>
        <button
          onClick={() => {
            console.log(username.value, password.value)
          }}
        >
          进入系统
        </button>
      </div>
    </div>
  )

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值