学习react
模块化
export (导出)
- 导出用来决定一个模块中哪些内容可以被外部查看
- 导出分成两种默认导出和命名导出
- 1、默认导出 export default xxx;
- 一个模块中只能有一个默认导出
- 2、命名导出
- export const x = xxx;
import(导入)
- 导入用来将外部模块中内容导入到当前模块中
- 1、导入默认模块 import xxx from "xxx";
- 导入默认模块时,变量名可以自主指定,无需和模块中的变量名对应
- 在网页中导入模块时,模块的路径必须写完整(./或../开头,扩展名也得写上)
- 2、命名导入 import { xxx,xxx} from "xxx"
class 类
- 类中的所有代码都会在严格模式下执行,严格模式下其中一个特点就是,函数的 this 不在是 window,而是 undefined
- 在类中方法的 this 不是固定的;以方法形式调用时,this 就是当前的实例;以函数形式调用,this 是 undefined
- 在开发时,在有些场景下,我们希望方法中的 this 是固定的,不会因调用方式不同而改变
- 如果遇到上述需求,可以使用箭头函数来定义类中的方法
- 如果类中的方法是以箭头函数定义的,则方法中的 this 恒为当前实例,不会改变
- this.xxx = this.xxx.bind(this); // 将 xxx 方法的 this 绑定为当前实例
继承
- 使用 extends 来继承一个类,继承后就相当于将该类的代码复制到了当前类中
- 当我们使用继承后,被继承的类就称为父类,继承父类的类称为子类
- 子类继承父类后,将获得父类中所有的属性和方法,也可以创建同名的属性或方法来对父类进行重写
- class xxx extends xxx {}
- 当在子类中重写父类构造函数时,必须在子类构造函数中第一时间调用父类构造函数,否则会报错
- constructor()
- 使用 static 开头的属性是静态属性,方法是静态方法
- 静态方法 this 不是实例对象而是当前的类对象
react
三个 API
React.createElement()
- 用来创建 React 元素
- React 元素无法修改
ReactDOM.createRoot()
-
获取跟元素
-
用来创建 React 的根容器,容器用来放置 React 元素
root.render()
-
将元素在根元素中显示
-
用来将 React 元素渲染到根元素中
-
根元素中所有的内容都会被删除,被 React 元素所替换
-
当重复调用 render()时,React 会将两次的渲染结果进行比较
-
他会确保只修改那些发生变化的元素,对 DOM 做最少的修改
JSX
- JSX 就是 React.createElement()的语法糖
- JSX 在执行之前都会被 babel 转换为 js 代码
state
-
state 实际就是一个被 React 管理的变量, 当我们通过 setState()修改变量的值时,会触发组件的自动重新渲染
-
只有 state 值发生变化时,组件才会重新渲染
-
当 state 的值是一个对象时,是用新对象替换旧对象
-
当通过 setState 去修改一个 state 时,并不表示当前的 state,它修改的是组件下一次渲染时 state 值
-
setState()会触发组件的重新渲染,它是异步的,所以当调用 setState()需要用旧 state 的值时,一定要注意有可能出现计算错误的情况,为了避免这种情况,可以通过为 setState()传递回调函数的形式来修改 state
-
改值
const newObj = Object.assign(target: {},obj)
newObj.name = "xxx"
setUser(newObj) -
setUser({...user,name: "xxx"})
useRef() 获取原生 dom 对象
- 慎用
- 1、创建一个存储 DOM 对象的容器 使用 useRef()钩子函数
-
const xxx = useRef()// 创建一个容器 -
钩子函数的注意事项:
-
(1)、React 中的钩子函数只能用于函数组件或自定义钩子
-
(2)、钩子函数只能直接在函数组件中调用
-
2、将容器设置为想要获取 DOM 对象元素的 ref 属性
-
<div ref={xxx}></div> -
useRef() 返回的就是一个普通的 js 对象,所以我们直接创建一个 js 对象,也可以代替 useRef()
-
区别: 我们创建的对象,组件每次重新渲染都会创建一个新对象;而 useRef()创建的对象,可以确保每次渲染获取到的都是同一个对象
-
当你需要一个对象不会因为组件的重新渲染而改变时,就可以使用 useRef();
类组件
-
webstrom 中的快捷方式:
-
rsc 函数组件(不带 props)
-
rsi 函数组件(带 props)
-
rcc 类组件
-
类组件中 state 统一存储到实例对象的 state 属性中,可以通过 this.state 来访问,通过 this.setState()对其进行修改
-
当我们通过 this.setState()修改 state 时,React 只会修改设置了的属性,仅限于直接存储在 state 中的属性
-
xxx = React.createRef() // 创建属性存储 DOM 对象
portal
- portal 可以将组件渲染到页面中的指定位置
- ReactDOM.createPortal(jsx,目标位置 DOM 元素)
create-react-app
-
npx creat-react-app 项目名
-
npm run eject 不可逆 设置 webpack
-
xxx.module.css // css 的模块
-
import xxx from "./xxx.module.css"; // 引入
-
css 模块可以动态的设置唯一的 calss 值
移动端
- 设置移动端的适配
- 除以几视口的宽度就是多少 rem,设置视口的总宽度为 750rem
- document.documentElement.style.fontSize = 100 / 750 + 'vw'
Context
-
不同组件间共享数据的方式。
-
Context 相当于一个公共的存储空间,我们可以将多个组件中都需要访问的数据统一存储到一个 Context 中,这样无需通过 props 逐层传递,即可使组件访问到这些数据
-
通过 React.createContext()创建 context
-
store 文件夹存放数据
-
xxx.js 文件
import React from 'react';
const TestContext = React.createContext({
name: '张三',
age: 18
})
export default TestContext;
组件 A
-
使用方式一
-
1、引入 context
-
2、使用 XXX.Consumer 组件来创建元素
-
Consumer 的标签体需要一个回调函数,它会将 context 设置为回调函数的参数,通过参数就可以访问到 context 中存储的数据
-
Consumer 消费者
import TestContext from "./xxx";
<TestContext.Consumer>
{(ctx) => {
return{ctx.name} - {ctx.age}
}}
</TestContext.Consumer>
组件 b
-
使用 Context 方式二;
-
1、导入 Context
-
2、使用钩子函数 useContext()获取到 context
-
useContext()需要一个 Context 作为参数
-
它会将 Context 中数据获取并且作为返回值返回
-
Xxx.Provider
-
表示数据的生产者,可以使用它来指定 Context 中的数据
-
通过 value 来指定 Context 中存储的数据,
-
这样一来,在盖组件的所有的子组件中都可以通过 Context 来访问它所指定数据。
-
当我们通过 Context 访问数据时,读取离他最近的 Provider 中的数据,
-
如果没有 Provider,则读取 context 中的默认数据
import TestContext from "./xxx";
// 使用钩子函数获取Context
const ctx = useContext(TestContext);
{ctx.name} ---{ctx.age} -
阻止冒泡:e.stopPropagation();
-
字体图标:fontawesome
-
React Developer Tools
useEffect
-
Effect 副作用
-
Too many re-renders
-
当我们直接在函数体中调用 setState 时,就好触发上述错误
-
setState()的执行流程(函数组件)
-
setCount() ===>dispatchSetDate()
-
会先判断,组件当前处于什么阶段
-
如果是渲染阶段--->不会检查 state 值是否相同
-
如果不是渲染阶段--->会检查 state 的值是否相同
-
如果值不相同,则对组件进行重新渲染
-
如果值相同,则不对组件进行重新渲染
-
如果值相同,React 在一些情况下会继续执行当前组件的渲染,但是这个渲染不会触发其子组件的渲染,这次渲染不会产生实际的效果
-
这种情况通常发生在值第一次相同时
-
useEffect()是一个钩子函数,需要一个函数作为参数
-
这个作为参数的函数,将会在组件渲染完毕后执行
-
在 useEffect()可以传递一个第二个参数
-
第二个参数是一个数组,在数组中可以指定 Effect 的依赖想
-
指定后,只有当依赖发生变化时,Effect 才会被触发
-
通常会将 Effect 中使用的所有的局部变量都设置为依赖项
-
这样一来可以确保这些值发生变化时,会触发 Effect 的执行
-
如果依赖项设置了一个空数组,则意味 Effect 只会在组件初始化时触发一次
-
降低数据过滤的次数,提高用户体验,
-
用户输入完了在过滤,用户输入的过程中,不用过滤
-
当用户停止输入动作 1 秒后,我们才做查询
-
在开启一个定时器的同事,应该关掉上一次
useEffect(() => {
const timer = setTimeout(() => {
},1000)// 在Effect的回调函数中,可以指定一个函数作为返回值
// 这个函数可以称其为清理函数,塔会在下次Effect执行钱调用
// 可以在这个函数中,做一些工作来清除上次Effect执行所带来的影响
return () => {
clearTimeout(timer)
}
},[xxx])
useReducer
-
可以对 state 进行整合的工具,翻译为“整合器”
-
useReducer(reducer,initialArg,init)
-
参数:reducer 整合函数,对于我们当前 state 的所有操作都应该在该函数中定义,该函数作为返回值,会成为 state 的新值
-
reducer 在执行时,会收到两个参数
-
state: 当前最新的 state
-
action 它需要一个对象,在对象中会存储 dispatch 所发送的指令
-
initialArg: state 的初始值,作用和 useState()中的值是一样
-
返回值 ---> 数组:
-
第一个参数,state 用来获取 state 的值
-
第二个参数,state 修改的派发器,通过派发器可以发送操作 state 的命令,具体的修改行为将会由另外一个函数(reducer)执行
-
const [count,countDispatch] = useReducer( (state,action) => { return state},1) -
可以根据 action 中不同的 type 来执行不同的操作
-
countDispatch({})
-
为了避免 reducer 会重复创建,通常 reducer 会定义到组件的外部
const xxx = (state, action) => {
switch (action.type) {
default:
return state;
case xxx:
return xxx;
}
}
React.memo
-
React.memo(Index)
-
React.memo()是一个高阶组件
-
它接收另一个组件作为参数,并且会返回一个包装过的新组件
-
包装过的新组件就会具有缓存功能
-
包装过后,只有组件的 props 发生变化时才会触发组件的重新渲染,否则总是返回缓存中结果
-
对函数缓存
useCallback()
-
useCallback 是一个钩子函数,用来创建 React 中的回调函数
-
useCallback 创建的回调函数不会总在组件重新渲染时重新创建
-
useCallback() 参数
-
1、回调函数
-
2、依赖数组
-
当依赖数组中的变量发生变化时,回调韩式才会重新渲染
-
如果不指定依赖数组,回调函数每次都会重新创建
-
一定要将回调函数中使用到的所有变量都设置成依赖项除了(setState)
const onAdd = useCallback(() => {
xxx
},[])
Strapi
- npm 安装
- npx create-strapi-app@latest demo --quickstart
- yarn
- yarn create strapi-app demo --quickstart
Fetch API
-
fetch 浏览器自带
-
featch() 用来向服务器发送请求加载数据,是 Ajax 的升级版
-
它需要两个参数:1、请求地址 2、请求信息(可省略)
fetch('xxx')
.then((res) => {
// 判断是否正常返回响应信息
if(res.ok) {
// response 表示响应信息
return res.json(); // 该方法可以将响应的json直接转换为js对象
}
// 代码运行到这里,说明没有成功加载数据
// 抛出一个错误
throw new Error('数据加载失败!')
})
.then(data => {
})
.catch((err) => {
// catch中的回调函数,用来统一处理错误
// catch 一执行,说明上述代码出错了})
await
useEffect(() => {
const fetchData = async () => {
try {
const res = await featch('xxx');
if(res.ok) {
const data = await res.json();
}else {
throw new Error('数据加载失败!')
}
}catch(e) {
}finally {
}
}
fetchData();
},[])
Redux
-
状态管理器
-
状态(state)
-
容器(Container)
-
可预测(Predictable)
-
subscribe (订阅)
-
dispatch (派发)
-
网页中使用 redux 的步骤:
-
1、引入 reduxh 核心包
-
2、创建 reducer 整合函数
-
3、通过 reducer 对象创建 store
-
4、对 store 中的 state 进行订阅
-
5、通过 dispatch 派发 state 的操作指令
const subBtn = document.getElementById('sub');
const addBtn = document.getElementById('add');
const countSpan = document.getElementById('countSpan')function reducer(state,action) {
switch (action.type) {
case "ADD":
return state + 1;
case "SUB":
return state -1;
default:
return state;
}
}
const store = Redux.createStore(reducer,1);
store.subscribe(() => {
countSpan.innerText = store.getState();})
subBtn.addEventListener('click',() => {
stroe.dispatch({type: 'SUB'})
})
addBtn.addEventListener('click',() => {
store.dispatch({type: 'ADD'})
})
Redux Toolkit(RTK)
-
Redux 工具包
-
npm 安装
-
npm install react-redux @reduxjs/toolkit -S
-
yarn 安装
-
yarn add react-redux @reduxjs/toolkit
-
store/index.js// 使用RTK来构建store
import { configureStore } from "@reduxjs/toolkit";
import { stuReducer } from "./stuSlice";
import { schoolReducer } from "./schoolSlice";
// 创建store 用来创建store 对象,需要一个配置对象作为参数
const store = configureStore({
reducer: {
student: stuReducer,
school: schoolReducer
}
})
export default store; -
store/schoolSlice.js// 创建学校的slice
import { createSlice } from "@reduxjs/toolkit";
const schoolSlice = createSlice({
name: 'school',
initialState: {
name: '花果山一小',
address: "花果山大街28号"
},
reducers: {
setName: (state,action) {
state.name = action.payload;
},
setAddress(state,action) {
state.address = action.payload;
}
}
})
export const { setName, setAddress } = schoolSlice.actions;
export const { reducer: schoolReducer } = schoolSlice; -
store/stuSlice.js// createSlice 创建reducer的切片
// 它需要一个配置对象作为参数,通过对象的不同属性来指定它的配置
import { createSlice } from "@reduxjs/toolkit";
const stuSlice = createSlice({
name: 'stu', // 用来自动生成action中的type
initialState: {
name: '孙悟空'.
age: 18,
gender: '男',
address: '花果山'
}, // state的初始值
reducers: { // 指定state的各种操作,直接在对象中添加方法
setName(state, action) {
// 可以通过不同的方法来指定对state的不同操作
// 两个参数,state这个state的是一个代理对象,可以直接修改
state.name = action.payload;
},
setAge(state, action) {
state.age = action.payload;
}
}
})
// 切片对象会自动的帮助我们生成action
// actions中存储的是slice自动生成action创建器(函数),调用函数后会自动创建action对象
// action对象的结构 {type:name/函数名,payload: 函数的参数}
export const { setName, setAge } = stuSlice.actions;
export const { reducer: stuReducer } = stuSlice; -
App.jsimport React from "react";
import { useDispatch, useSelector } from "react-redux";
import { setName, setAge } from "./store/stuSlice";
import { setName as setSchoolName, setAddress as setSchoolAddress } from "./store/schoolSlice";
const App = () => {
// useSelector() 用来加载state中的数据
// const student = useSelector(state => state.student);
// 引入学校的state
// const school = useSelector(state => state.school);
const [ student,school ] = useSelector(state => state);
// 通过useDispatch()来获取派发器对象
const dispatch = useDispatch();
// 获取action的构建器
const setNameHandler = () => {
dispatch(setName('沙和尚'))
}
const setAgeHandler = () => {
dispatch(setAge(33))
}
return (
{student.name} ---{student.age} --- {student.gender} --- {student.address}
{school.name} --- {school.address}
<button onClick={() => dispatch(setSchoolName('高老庄中小'))}>修改学校名字
<button onClick={() => dispatch(setSchoolAddress('高老庄19号'))}>修改学校地址
)
} -
index.jsimport ReactDOM from "react-dom/client";
import App from "./app";
import { Provider } from "react-redux";
import store from "./store";const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
)
RTKQ
- RTK Query
-
store\studentApi.jsimport { createApi, fetchBaseQuery } from "@reduxjs/toolkit/dist/query/react";
// 创建Api对象
// createApi() 用来创建RTKQ中的API对象
// RTKQ的所有功能都需要通过该对象来进行
// createApi()需要一个对象作为参数
const studentApi = createApi({
reducerPath: "studentApi", // Api的标识,不能和其他的API或reducer重复
baseQuery: fetchBaseQuery({
baseUrl: 'https:xxxx/api/'
}),// 指定查询的基础信息,发送请求使用的工具
endpoints(build) {
// build是请求的构建器,通过build来设置请求的相关信息
return {
getStudents: build.query({ // 删除/新增/编辑改成mutation
query() {
// 用来指定请求子路径
return "xxx";
},
// transformResponse 用来转换响应数据的格式
transformResponse(baseQueryReturnValue, meta, arg) {
return baseQueryReturnValue.data
},
keepUnusedDataFor: 0, // 设置数据缓存的时间,单位:秒 默认60S
})
}
} // endpoints 用来指定Api中的各种功能,是一个方法,需要一个对象作为返回值
})
// Api对象创建后,对象中会根据各种方法自助的生成对应的钩子函数
// 通过这些钩子函数,可以来想服务器发送请求
// 钩子函数的命名规则 getStudents --> useGetStudentsQuery
export const { useGetStudentsQuery } = studentApi;
export default = studentApi; -
store/index.jsimport { configuerStore } from "@reduxjs/toolkit";
import studentApi from "./studentApi";
const store = configureStore({
reducer: {
[studentAPi.reducerPath]: studentApi.reducer
},
middleware: getDefaultMiddleware =>
getDefaultMiddleware().concat(studentApi.middleware)
});
setupListeners(store,dispatch); // 设置以后,将会支持 refetchOnFocus 和refetchOnReconnect
export default store; -
App.jsimport React from "react";
import { useGetStudentsQuery } from "./store/studentApi";
const App = () => {
// 调用Api查询数据
// 这个钩子函数它会返回一个对象作为返回值,请求过程中相关数据都在该对象中存储
const { data, isSuccess, isLoading } = useGetStudentsQuery(); // 调用Api中的钩子查询数据
return (
{ isLoading &&数据加载中
}
{ isSuccess && data.data.map(item =>
)}
{item.attributes.name}
)
}
-
index.jsimport ReactDOM from "react-dom/client";
import App from "./app";
import { Provider } from "react-redux";
import store from "./store";const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
)
useQuery 的返回值
- 返回对象
- currentData: undefined // 当前参数的最新数据
- data: undefined // 最新的数据
- isError: false // 布尔值,是否有错误
- error: Error() // 对象,有错时才存在
- isFetching: true // 布尔值,数据是否在加载
- isLoading: true // 布尔值,数据是否第一次加载
- isSuccess: false // 布尔值,请求是否成功
- isUninitialized: false // 布尔值,请求是否还没有开始发送
- refetch: f() // 一个函数,用来重新加载数据
- status: "pending" // 字符串,请求的状态
useQuery 的参数
- useQuery 可以接收一个对象作为第二个参数,通过该对象可以对请求进行配置
- selectFromResult: result => { return result; } // 用来指定 useQuery 返回的结果
- pollingInterval: 0, // 设置轮询的间隔,单位毫秒 如果为 0 则表示不轮询
- skip: false, // 设置是否跳过当前请求,默认 false
- refetchOnMountOrArgChange: false, // 设置是否每次都重新加载数据 false 正常使用缓存 true 每次都重载数据 数字,数据缓存的时间(秒) 默认是 false
- refetchOnFocus: false , // 是否在重新获取焦点时重载数据 默认 false
- refetchOnReconnect: false, // 是否在重新连接后重载数据 默认 false
React Router
- Router 版本 5
- npm 安装 npm install react-router-dom@5 -S
- yarn 安装 yarn add react-router-dom@5
- Link
- NavLink
- Prompt 组件(离开页面出现确认效果)
- Redirect 组件(重定向)
-
Route V6
-
Routes V6 中新增的组件,作用和 Switch 类似,都是用于 Route 的容器,Routes 中 Route 只有一个会被匹配
-
需要通过 element 来指定要挂载的组件
<Route path="/" element={}>
-
可以使用 useParams()来获取参数
-
useLocation() 获取当前的地址信息
-
useMatch() 检查当前 url 是否匹配某个路由
-
如果路径匹配,则返回一个对象,不匹配则返回 null
-
const match = useMatch('/about')
-
useNavigate() 获取一个用于跳转页面的函数
const nav = useNavigate();
const clickHandler = () => {
nav('/about') // 只用push,会产生历史记录
nav('/about', {replace: true}) // 使用replace不会产生新的记录
} -
路由嵌套
-
App.js
<Route path="/" element={}>
<Route path="about" element={}>
<Route path="hello" element={}>
-
About.jsreturn (
<>
</>
) -
Outlet
-
Outlet 用来表示嵌套路由中的组件
-
当嵌套路由中的路径匹配成功了,Outlet 则表示嵌套路由中的组件
-
当嵌套路由中的路径没有匹配成功,Outlet 就什么都不是
Navigate 组件
-
Navigate 组件用来跳转到指定的位置
-
默认使用 push 挑战
-
可以加 replace 属性
import { Navigate } from "react-router-dom"
return(
<>
</>
)
NavLink
import { NavLink } from "react-router-dom"
<NavLink style={({isActive}) => {
return isActive? { backgroundColor: 'yellow'} : null
}} to="/home">
NeedAuth 组件
-
登录权限
-
NeedAuth.jsimport React from "react"
import { useSelector,useLocation } from "react-redux"
import { Navigate } from "react-router-dom"const NeedAuth = props => {
const auth = useSelector(state => state.auth);
const location = useLocation();
return auth.isLogged ? props.children : <Navigate to={"/login"} replace state={{preLocation: location}} />
}
export default NeedAuth; -
App.js
<Route path={"/"} element={}>
<Route path={"profile"} element={ }>
<Route path={"login"} element={}>
-
跳转页面到之前的目录
const location = useLocation();
const form = loaction.state?.preLocation?.pathname || "/";
navigate(from,{replace: true}) -
redux 存储在内存中刷新后就不见了
useMemo
-
React 中自带的钩子函数
-
useState
-
useEffect
-
useContext
-
useReducer
-
useCallback
-
useRef
-
useMemo
-
useImperativeHandle
-
useLayoutEffect
-
useDebugValue (18.0 新增)
-
useDeferredValue(18.0 新增)
-
useTransition(18.0 新增)
-
useId(18.0 新增)
-
useSyncExternalStore(18.0 新增)
-
useInsertionEffect(18.0 新增)
const result = useMemo(() => {
function sum(a,b) {
return a + b;
}
},[a,b])
- useMemo 用来存储函数的执行结果
useImperativeHandle
- 子组件通过 forwardRef 包装 ,父组件可以拿到 ref
- React.forwardRef()
- 可以用来指定组件向外部暴露的 ref
- useImperativeHandle 和 forwardRef 结合使用
-
useImperativeHandle 可以用来指定 ref 返回的值
let inputRef = useRef();
useImperativeHandle(ref,() => {
//回调函数的返回值,会成为ref的值
return {
changeInpValue(val) {
inputRef.current.value = val;
}
}
})
useEffect
- 组件挂载-->state 改变-->DOM 改变-->绘制屏幕-->useEffect
useInsertionEffect
- 组件挂载--> state 改变-->useInsertionEffect-->DOM 改变-->绘制屏幕
- 添加元素
useLayoutEffect
- 使用场景不多
- 组件挂载--> state 改变-->DOM 改变-->useLayoutEffect --> 绘制屏幕
- 调整元素样式
- 执行顺序 useInsertionEffect -- > useLayoutEffect -->useEffect
UseDebugValue
- 使用场景不多
useDeferredValue
- 设置 state 的延迟值
- useDeferredValue 需要一个 state 作为参数,会为 state 创建一个延迟值
- 当设置了延迟值后,每次 state 修改时都会触发两次重新的渲染
- 这两次执行对于其他的部分没有区别,但是延迟值两次执行的值是不同的
- 第一次执行时延迟值是 state 的旧值,第二次执行时,延迟值是 state 的新值
- 延迟值,总是会比原版的 state,慢一步更新
useTransition
- 使用 state 的优先级
- startTransition 的回调函数中设置 setState 会让其他的 setState 生效后才执行
- const [isPending,startTransition] = useTransition();
UseId
- 生成一个 id,避免重名
- const id = useId();

浙公网安备 33010602011771号