❤️ Author: 老九
☕️ 个人博客:老九的CSDN博客
🙏 个人名言:不可控之事 乐观面对
😍 系列专栏:
路由
react-router的基本使用
react-router最主要的API是给我们提供了一些组件
BrowserRouter使用history模式
HashRouter使用hash模式
用这两个组件将App组件进行包裹
在Router5.x版本,使用的是Switch组件
在App组件中(注意不是index.js文件中),Router5.x使用的Routes组件,Routes组件中使用一个一个的Route
Route有path和element两个属性,Router5.x中属性是component,6.x中使用的是element,element传入的是组件的实例
路由跳转
- 通过Link组件就可以点击跳转
NavLink
通过NavLink,就是Link带了一个class:active
如果要是自定义样式的话,style属性里面传入一个函数
这里的style要区别于元素的style,元素的style是传入一个对象,而组件的style传入的是一个函数
style传入的函数,参数是一个对象,这个对象可以解构出isActive
- 如果想修改默认的类名
补充:
如果要把那个函数提出来,不需要写成箭头函数,直接this.getActiveClass就可以了,因为写成箭头函数的目的是如果getActiveClass函数中使用了this,才通过箭头函数获取到最外层的this,否则是不需要加箭头函数的
Navigate导航组件
- 用于路由重定向,当这个组件出现时,就会执行跳转到对应的to路径中
也可以直接重定向
NotFound
路由的嵌套
第一步:在App.jsx中写定义好路由的嵌套映射关系
第二步:在home组件中写link表示跳转到哪,注意需要一个outlet占位符组件
useNavigate
- 通过useNavigate的hook函数实现手动跳转,因为link组件就是一个a标签,我们用useNavigate可以实现自定义标签的跳转了
- 注意hook只能在函数组件中使用,并且hook函数必须写在代码的最上面
整体代码:
在类组件中使用useNavigate
如果需要在类组件中用useNavigate,需要通过高阶组件进行拦截封装
下面的代码是在home类组件中添加一个歌单的按钮,进行跳转,也就是home组件中可以跳转到homesongmenu组件
首先写一个高阶组件,返回的是一个函数组件,因为只有在一个函数组件中,才能使用hook函数中的useNavigate,然后将这个navigate通过参数的方式传给类组件,类组件通过props接受这个router
总体代码
路由传递参数
通过传入的id进行页面跳转
通过动态路由
在类组件中,通过高阶函数使用useNavigate,实现页面的跳转
总体代码(最后一行没截全):
获取url上的参数
然后通过useParams高阶函数获取到路由参数上的值
通过useLocation或者ueSearchParams获取查询字符串
当出现跳转到这种页面的时候
通过useLocation获取问号后面的值
但是这样的话,取值的时候还需要自己处理字符串
如果通过useSearchParams的hook函数,就可以直接获取到参数的值
useSearchParams()函数返回一个数组,取值的时候,用get方法即可
也可以这样写:
返回的就是一个对象,更方便一些
route配置抽取
将原来写在App中content类下面的routes一大球改成使用hook函数的写法
建一个router的文件夹
在index.js中写入之前那一大球的routes,route等(只是换了一种写法)
最后将routes导出放在useRoutes的hook函数参数中即可
路由懒加载
在router包中的index.js文件中加载路由时通过React中的lazy函数实现路由懒加载
如果我们对某些组件进行了异步加载(懒加载),那么App组件需要使用Suspense进行包裹,fallback里面写的是如果异步加载没有加载完成的时候,先展示fallback里面的组件
Hook
- 为什么需要Hook?让我们在不编写类组件的情况下使用state以及其他的React特性(生命周期)
- 和类组件不同,函数式组件当我们修改state的时候,组件不知道重新进行渲染,并且生命周期也没有
- rfce可以快速创建函数式组件 ,如果要包裹一个memo,就要用rmc
- 使用hook的话只能在函数的顶层调用hook,只能在函数式组件中使用Hook,如果要在类组件中使用hook需要通过高阶函数
useState
- useState小括号里传入初始值,返回一个数组,第一个是值,第二个是方法,数组解构
使用useState
useState的原理
一般来说,在函数return退出后,变量就会消失,但是state中的变量会被React保留,当我们点击button的时候,执行了changeMessage函数,changeMessage中有一个setMessage函数,React内部实现了这个setMessag函数,传入了一个newMessage,React内部会保留下来这个newMessage,当我们调用了setMessage函数,React就会重新渲染这个函数组件(类似于重新执行render),当发现又执行了useState的,React就会把保存下来的newMessage给返回,展示到页面上
useState接受唯一一个参数,在第一次组件被调用时是用来作为初始化值,如果没有传递参数,那么初始化值是undefined
useEffect
- Effect可以让你来完成一些类似于class中声明周期的功能,类似于完成网络请求,手动更新DOM,一些事件的监听
- 通过useEffect的Hook,可以等待React页面渲染完成后执行某些操作,useEffect要求我们传入一个回调函数,在React执行完更新DOM操作后,就会回调这个函数
- 注意这个hook是在函数组件每次渲染完成后,都会执行一次这个函数
- 一个函数组件中可以有多个useEffect,是按照顺序执行的
下面是一个例子,根据count的值更改title
需要清除的Effect(componentWillUnmount)
比如我们之前都需要在componentWillUnmount中有对应的取消订阅,useEffect中的返回值是一个回调函数,在组件被重新渲染或者组件被卸载的时候执行
Effect性能优化
- 因为每个Effect在组件每次重新加载都执行一次,但是如果需求是从服务器取数据,那么我们就取一次就可以,所以某些代码我们希望执行一次即可
- useEffect可以传第二个参数,第二个参数是一个数组,根据数组里的值改变了,才重新执行回调函数
下面这个代码,数组里面什么都没写,就代表这个useEffect谁的影响都不受,只执行一次
如果第二个参数不传的话,就是无论什么情况,只要组件重新渲染,该useEffect就重新执行
补充useLayoutEffect和useEffect的区别
看下面的代码:
通过useLayoutEffect可以实现,在内容还没有显示在屏幕上之前,发现数据是不对的,我们就可以在这个hook函数中修改数据
例子
useContext
这个直接就更方便的使用contextAPI了
首先创建创建一个context文件夹,里面创建一个index.js 的文件,在index文件中创建两个context实例,并export出去
然后在root的index.js中,写上要提供的数据Provider
使用时候,不需要写什么consumer了,直接就使用usecontext,返回的值就是写在value中的值
useCallback
- 问题:当我们重新调用函数组件的时候,函数组件中的函数就再次被定义了
- useCallback会返回一个函数的记忆值,在依赖不变的情况下(根据第二个参数判断),多次定义的时候,返回的值是相同的
- 当我们需要将一个函数传递给子组件的时候,最好使用useCallback进行优化,将优化之后的函数传递,防止重新渲染父组件的时候,子组件的函数也跟着重新调用
下面这个例子,当点击改变文本按钮时候,就会重新执行App组件,如果increment没有用useCallback包含,就会重新定义increment,而increment函数传递给了子组件,子组件的props发生改变,组件本身也会被重新渲染,所以子组件也跟着重新执行了一遍,所以需要将increment函数包裹在useCallback中,就可以避免子组件重新渲染的bug
进一步优化
上面使用usecallback,解决了如果重复渲染父组件,其中父组件的函数传递到了子组件,子组件也导致重新渲染的问题。
接下来我们要优化的是,当count发生改变的时候,使用同一个函数,而不是每次都定义一次函数。
方法一
将数组中count的依赖移除掉,虽然都是使用同一个函数,但是会导致闭包陷阱的问题,每次传入的count都是useState的初始值
闭包陷阱,如果上面的代码数组中不传入count,就会一直用第一个函数,相当于下面的bar1,等同于useCallback参数中接受到的count一直都是0
方法二
使用useRef,这个hook在组件多次渲染时,返回同一个值
我们通过将count变量赋值给了contRef的current属性上,从而就解决了闭包陷阱的问题,每次传入的count值就是增加过的值,通过useRef返回的都是同一个值
useMemo
- useMemo优化的是函数的返回值。而useCallback优化的是函数的本身
- useMemo接受一个回调函数,返回一个函数计算后的值,useCallback接受一个函数,返回一个优化后的函数
- 类似于vue的computed,对于某一个属性的操作比较复杂,而每次重新渲染函数组件的时候,重新计算复杂的数据就会影响性能,通过useMemo就可以判断如果数据没有发生变化,就使用之前的数据就可以了
usememo的第二个作用是在向子组件传递对象的时候,可以包一个usememo,对于普通的变量,如果数值没有发生改变,父组件重新渲染,子组件也不会重新渲染,但是对于对象来说,父组件重新定义了对象,传递给子组件后,即使对象里的值没有发生改变,子组件也会重新渲染,因此使用usememo进行包裹
完整代码:
import React, { memo, useMemo, useState } from 'react';
const HelloWorld = memo(function (props) {
console.log("Hello world 被渲染");
return <h2>Hello World</h2>
})
function calcNumTotal (num) {
// console.log("计算过程重新调用");
let total = 0
for (let i = 1; i < num; i++){
total += i
}
return total
}
const App = memo(() => {
const [count,setCount] = useState(0)
let result = useMemo(() => {
return calcNumTotal(count)
}, [count])
// const info = { name: "why", age: 18 }
const info = useMemo(()=>({name:"why",age:18}),[])
return (
<div>
<h2>计算结果: {result}</h2>
<h2>计数器:{count}</h2>
<button onClick={e => setCount(count + 1)}>+1</button>
<HelloWorld result={result} info={info} />
</div>
)
})
export default App
usememo和usecallback的异同
下面这段代码的increment和increment2是等同的
官方文档中是这么说的
通常使用useCallback的目的是不希望子组件进行多次渲染,并不是为了函数进行缓存
useRef
useRef返回一个ref对象,返回的ref对象在组件的整个生命周期保持不变
用法一:获取DOM
用法二:返回的总是同一个对象
通过这个用法,就可以解决usecallback的闭包陷阱了
自定义Hook
- 自定义Hook本质上只是一种函数代码逻辑的抽取,hook本身就是一个函数
- 注意:自定义函数的名字必须用use开头
下面举个例子,抽取useLogLife函数,下面代码中useEffect的数组依赖中可以写个cName,就不报黄色波浪线了
抽取context
首先文件的目录结构是这样的
使用createContext创建context
外层index.js中用Context.Provider,为对应的context提供数据
创建hooks文件夹后,抽离使用context的逻辑
在hooks的index.js文件中导出自定义的hook
在app.js中使用自定义Hook
监听滚动位置
监听的逻辑属于函数组件的副作用,除了渲染页面的逻辑,其他都属于副作用,类似axios请求数据,监听滚动页面等
因此监听的逻辑需要放在useEffect中,并且不依赖任何变量,只执行一次,useEffect后面的数组参数什么都不写
自定义hook
使用自定义hook
自定义LocalStorage
写localStorage的自定义hook
使用localstorage的自定义hook
————————————————————————
♥♥♥码字不易,大家的支持就是我坚持下去的动力♥♥♥
版权声明:本文为CSDN博主「亚太地区百大最帅面孔第101名」的原创文章