学习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 <div>{ctx.name} - {ctx.age}</div> }} </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); <div>{ctx.name} ---{ctx.age}</div>
-
阻止冒泡: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) { <!-- state表示当前state,可以根据这个state生成新的state action是一个js对象,它里边会保存操作的信息 --> 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.js
import 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 ( <div> <p>{student.name} ---{student.age} --- {student.gender} --- {student.address}</p> <button onClick={setNameHandler}>修改name</button> <button onClick={setAgeHandler}>修改age</button> <p>{school.name} --- {school.address}</p> <button onClick={() => dispatch(setSchoolName('高老庄中小'))}>修改学校名字</button> <button onClick={() => dispatch(setSchoolAddress('高老庄19号'))}>修改学校地址</button> </div> ) }
-
index.js
import 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( <Provider store={store}> <App /> </Provider> )
RTKQ
- RTK Query
-
store\studentApi.js
import { 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.js
import { 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.js
import React from "react"; import { useGetStudentsQuery } from "./store/studentApi"; const App = () => { // 调用Api查询数据 // 这个钩子函数它会返回一个对象作为返回值,请求过程中相关数据都在该对象中存储 const { data, isSuccess, isLoading } = useGetStudentsQuery(); // 调用Api中的钩子查询数据 return ( <div> { isLoading && <p>数据加载中</p>} { isSuccess && data.data.map(item => <p key={item.id}> {item.attributes.name} </p>)} </div> ) }
-
index.js
import 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( <Provider store={store}> <App /> </Provider> )
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 来指定要挂载的组件
<Routes> <Route path="/" element={<Home />}></Route> </Routes>
-
可以使用 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
<Routes> <Route path="/" element={<Home />}></Route> <Route path="about" element={<About />}> <Route path="hello" element={<Hello />}></Route> </Route> </Routes>
-
About.js
return ( <> <Outlet /> </> )
-
Outlet
-
Outlet 用来表示嵌套路由中的组件
-
当嵌套路由中的路径匹配成功了,Outlet 则表示嵌套路由中的组件
-
当嵌套路由中的路径没有匹配成功,Outlet 就什么都不是
Navigate 组件
-
Navigate 组件用来跳转到指定的位置
-
默认使用 push 挑战
-
可以加 replace 属性
import { Navigate } from "react-router-dom" return( <> <Navigate to="/student" replace> </> )
NavLink
import { NavLink } from "react-router-dom"
<NavLink style={({isActive}) => {
return isActive? { backgroundColor: 'yellow'} : null
}} to="/home"></NavLink>
NeedAuth 组件
-
登录权限
-
NeedAuth.js
import 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
<Routes> <Route path={"/"} element={<HomePage />}></Route> <Route path={"profile"} element={<NeedAuth><ProfilePage /></NeedAuth>}> <Route path={"login"} element={<LoginPage />}> </Routes>
-
跳转页面到之前的目录
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 123; --> 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();