react第一天
创建项目
全局安装包create-react-app:npm install -g create-react-app
创建新的项目:create-react-app 项目名
启动项目:npm run start
JSX
JSX: javascript+xml。在js中编写HTML模板结构。是js的语法扩展,需要解析工具解析之后才能在浏览器中运行。
key的作用
react框架内部使用,提升页面的更新性能
事件绑定
on + 事件名称
点击事件
获取event对象
function click(e){
console.log(e)
}
function clickTwo(a, e){
console.log(e)
}
// 没有实参
<button onClick="handleClick">按钮1</button>
// 有实参,要用箭头函数,否则会立即执行
<button onClick="(e)=>handleClick('a', e)">按钮2</button>
useState
向组件添加一个状态变量,从而控制组件的渲染效果。数据驱动视图
useState是一个函数,返回值是一个数组。数组的第一个值是状态变量;第二个值是一个函数,用来修改状态变量的。
useState的参数将作为状态变量的初始值。如下,count初始值就是 0
function App() {
const [ count, setCount ] = useState(0);
const [obj, setObj ] = useState({name: 'jerry'})
function counterClick(){
setCount(count+1)
}
function switchName(){
setObj({
...obj,
name: obj.name === 'lucy' ? 'jerry' : 'lucy'
})
}
return (
<div className="App">
<button onClick={counterClick}>{count}</button>
<div>{ obj.name }</div>
<button onClick={switchName}>切换name</button>
</div>
);
}
修改状态的规则
状态不可变
状态是只读的,应该替换它而不是修改它。直接修改不能引发视图更新。对于对象类型的状态变量,应该始终传给set方法一个全新的对象来进行修改。
样式
React组件基础的样式控制有两种方式:
- 方式1:直接写在行内
- 方式2:class类名控制
// 方式1
<div style={{color: 'red'}}>我是div</div>
// 方式2
// index.css
.foo{
color: red
}
import './index.css';
function App(){
return (
<div className="foo">我是div</div>
)
}
react第二天
useRef
在React组件中获取/操作DOM,需要使用useRef钩子函数,步骤可以分为两步:
第一步:使用useRef创建ref对象,并与jsx绑定
第二步:在DOM可用时,通过ref对象的current拿到Dom对象
import { useRef } from "react"
export default function Ref(){
const btnRef = useRef(null);
const handleBtnClick = () => {
console.log( btnRef.current );
};
return (
<button onClick={handleBtnClick} ref={btnRef}>获取ref</button>
)
}
组件通信
1.父子组件通信
实现步骤
1.父组件传递数据 - 在子组件标签上绑定数据
2.子组件接收数据 - 子组件通过props参数接收数据(函数里接收一个props参数)
props可以传递任意类型的值,是一个只读对象。在子组件中不能进行修改,父组件的数据只能由父组件进行修改
特殊的props:children
场景:当我们把内容嵌套在子组件标签中,子组件会自动在名为children的props属性中接收该值
3.子传父:在子组件中调用父组件的函数,并传递实参
2.兄弟组件通信
使用状态提升实现兄弟组件之间的通信。借助“提升机制”,通过父组件进行兄弟组件之间的数据传递。
(就是改父组件的数据,父组件的数据改了之后,引用改数据的子组件也会同步的发生变化。兄弟组件引用的是同一个数据源)
3.跨层组件通信
使用context机制。
1.使用createContext方法创建一个上下文对象Ctx
2.在顶层组件(App)中通过Ctx.Provider(是一个高阶组件,不是函数)提供数据。Provider接收一个value属性,当value变化时候,内部的所有组件都会被重新渲染
3.在底层组件(B)中通过useContext(使用上下文对象)钩子函数取使用数据。useContext函数的参数必须是定义的context自身
useContext
用于跨组件之间的通信。代码示例所示。主要分为三步:
1)用createContext创建一个上下文:MyContext
2)使用context提供的高阶组件Provider,用value属性注入要传递的值:<MyContext.Provider value={value}></MyContext.Provider>
3)使用useContext使用注入的值,useContext的入参必须为创建的上下文本身(MyContext):const value = useContext(MyContext)。
react第三天
useEffect
1.用法说明
用于在React组件中创建不是由事件引起而是由渲染本身引起的操作,比如发送ajax请求,更改DOM等例如页面加载完毕后掉服务。
语法:useEffect(()=>{ *** }, [])
参数1: 是一个函数,也叫做副作用函数。在函数内部可以放置要执行的操作
参数2:是一个数组(可选),在数组里放置依赖项,不同依赖项会影响第一个函数的执行。当时一个空数组时候,副作用函数只会在组件渲染完成之后执行一次。
2.useEffect依赖项说明
-
没有依赖项 -- 组件初始渲染+组件更新时执行
-
空数组依赖项 -- 只在组件渲染时执行一次
-
添加特定依赖项 -- 组件初始渲染 + 特性依赖项变化时执行
3.useEffect -- 清除副作用
在useEffect中return出一个函数,执行卸载时候的清理操作
代码示例:
自定义hook
1.声名一个以use开头的函数
2.在函数体内封装可复用的逻辑
3.把组件中要用到的数据和函数return出去
4.在组件里调用这个函数,解构出来状态和回调函数进行使用
知识点补充
1.react中的fetch方法?
参考文档:www.python100.com/html/116010…
2.利用axios和json-server模拟接口请求?
1.安装axios和json-server插件
2.在根目录下创建json文件(如 db.json),里边存放模拟请求的数据
3.在package.json里面的script对象里添加配置:"server": "json-server db.json --port 5200"
4.启动命令:npm run server,就可以启动本地服务:http://localhost:5200
5.axios调用这个服务,获取到模拟数据
react第四天
react-redux & reduxjs/toolkit
redux:集中状态管理工具,可以独立于框架运行。
本专题使用reduxjs/tooltkit 和 react-redux来实现redux和react的融合
npm install @reduxjs/toolkit react-redux
react-redux负责把redux和react连接起来,内置的Provider组件通过store参数把创建好的store实例注入到应用中,链接正式建立。
dispatch:用来派发action的事件
RTK:redux toolkit
CRA:create-react-app
不理解的点记录
1.reduxjs/toolkit异步修改操作
使用步骤记录
以实现计数器为例
1)先安装reduxj/toolkit 和 react-redux插件
2)创建一个store文件夹,包含modules文件夹和index.js文件
3)在module文件夹里创建counterReducer.js文件
4)引入createSlice函数并调用
5)解构出action方法
6)创建counterReducer
7)导出供外部使用
8)在index.js文件里引入counterReducer,导入configureReducer函数,组合一个根store并导出
9)在根组件index.js里使用react-redux的高阶组件Provider,传入store
10)在counter.js业务组件里,引入步骤5导出的action
11)在业务组件里引入useSelector,使用store中的状态数据
12)在业务组件中引入useDispatch,生成一个dispatch,用于提交action
完整示例代码如下:
import { createSlice } from '@reduxjs/toolkit';
const counterStore = createSlice({
name: 'counter',
initialState: {
count: 0
},
reudcers: {
increment: (state, action){
state.count += action.payload;
},
decrement: (state){
state.count --;
}
}
})
const { increment, decrement } = counterStore.actions;
const counterReducer = counterStore.reducer;
export { increment, decrement };
export default counterReducer;
// index.js store根文件
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './modules/counter.js';
const store = configureStore({
reducer: {
counter: counterReducer
}
});
export default store;
// index.js 根文件
import { Provider } from 'react-redux';
import store from './store/index.js';
<Provider store={store}>
<App />
</Provider>
// counter.js 业务组件
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './store/module/counterReducer.js';
export default function Counter(){
const dispatch = useDispatch();
const { count } = useSelector( state => state.counter );
return (<div>
<button onClick={()=>dispatch(decrement())}>-</button>
{ count }
<button onClick={()=>dispatch(increment(2))}>-</button>
</div>)
}
react-router-dom
路由导航
路由系统中多个路由之间需要进行路由跳转,并且在跳转的同时有可能还会存在传递参数进行通信。
声名式导航
在模板中通过react-router-dom提供的Link组件描述要跳到哪里去。Link组件会被解析成a标签,to属性会被解析成href属性。如需要传参,直接进行字符串拼接。可以用在菜单配置等场景
编程式导航
使用react-router-dom提供的钩子useNavigate得到导航方法,然后以命令式的方式进行页面跳转。更加灵活。在js代码里执行跳转
import { Link, useNavigate } from "react-router-dom";
function App() {
const navigate = useNavigate();
return (
<div className="App">
// 声名式导航
<Link to="/list">列表</Link>
// 编程式导航
<div onClick={()=>navigate('/detail')}>跳到详情页</div>
</div>
);
}
export default App;
额外记录
createBrowserRouter创建的对象,也有一个navigate属性,可以做页面跳转
router.navigate('/')
路由导航传参
searchParams传参
1.跳转代码里路由上拼接参数,?方式拼接
2.接收时使用useSearchParams()函数,解构出params
3.用params.get(key)方法指定要取的入参字段
// 源文件
import { useNavigate } from 'react-router-dom';
export default function App(){
const navigate = useNavigate();
return (
<div onClick={()=>navigate('/detail?id=1')}>searchParams传参</div>
)
}
// 目标文件
import { useSearchParams } from 'react-router-dom';
export default function Detail(){
const [ params ] = useSearchParams();
const id = params.get('id');
}
params传参
1.路由配置里预留动态参数的位置。路由占位符
2.跳转代码里,参数拼在路由上,斜杠拼接
3.接收时用useParams()接收。key值与动态路由上的要保持一致
// 路由文件
import { createBrowserRouter } from 'react-router-dom';
import Detail from './pages/detail';
const router = createBrowserRouter([
{
path: '/detail/:id', // 路由的动态参数
element: <Detail />
}
])
// 源文件
import { useNavigate } from 'react-router-dom';
export default function App(){
const navigate = useNavigate();
return (
<div onClick={()=>navigate('/detail/1')}>searchParams传参</div>
)
}
// 目标文件
import { useParams } from 'react-router-dom';
export default function Detail(){
const params = useParams();
const id = params.id;
}
嵌套路由
在一级路由中又嵌套了其他路由,这种关系就叫做嵌套路由。
嵌套到一级路由内的路由又称作二级路由。
1.使用children属性配置路由嵌套关系
2.使用组件配置二级路由的渲染位置
配置默认二级路由
{
children: [
{
index: true, // 默认二级路由
element: <List />
},
...
]
}
路由懒加载
路由的静态资源只有在访问时才会动态获取,目的是为了优化项目首次打开的时间
如何配置?
1.把路由修改为React提供的lazy函数进行动态导入
2.使用React内置的Suspense包裹路由中的element选项对于的组件
import { lazy, Suspense } from 'react';
// 引入懒加载文件
// es6 import()。 import命令式同步引入,import()函数动态引入
const Home = lazy(()=>import('@/pages/Home'));
const store = createBrowserRouter([
// 使用Suspense的时候,一定要定义fallback的内容,是组件加载中占位的内容。可以是一个封装的组件。为空的话会报错
{path:'/', element:<Suspense fallback={'加载中'}}><Home/></Suspense>
])
404路由配置
以*作为路由的path
{
path: '*',
element: <NotFound />
}
两种路由模式
history
- 由createBrowserRouter创建
- 表现:url/login
- 底层原理:History对象 + pushState事件
- 需要后端支持
hash
- 由createHashRouter创建
- 表现:url/#/login
- 底层原理:监听hashChange事件
- 不需要后端支持
核心API
- createBrowserRouter
- createHashRouter
- useNavigate
- useSearchParams
- useParams
- RouterProvider组件
- Link组件
- Outlet组件
react第五天
useMemo
用于消耗非常大的计算,优化计算密集型的操作或者昂贵的函数调用。接收一个计算函数和一个依赖项作为入参,并返回计算结果。
只有在依赖项发生变化的时候才会进行重新计算,没有变化就会使用上一次的计算结果。提高性能。
import { useMemo } from 'react';
const memorizedValue = useMemo(()=>{
// 计算函数。应当是纯函数,不应有其他任何副作用。如果执行副作用或者异步操作,推荐使用 useEffect 钩子函数
// 返回计算结果
}, [dependency1, dependency2,...])
react第六天
配置@别名路径
利用craco插件。
-
1)引入craco插件: @craco/craco
-
2)配置craco.config.js文件(配置别名信息)
-
3)配置jsconfig.json文件(vscode使用,用来联想出路径)
-
4)更改项目启动命令
// craco.config.js const path = require('path'); module.exports = { // webpack配置 webpack: { // 配置别名 alias: { // 约定:使用 @ 表示 src 文件所在路径 "@": path.resolve(__dirname, 'src') } } } // jsconfig.json { "compilerOptions": { "baseUrl": "./", "paths": { // 联想,* 是通配符 "@/*": [ "src/*" ] } } } // 启动命令 "start": "craco start", "build": "craco build",
Token持久化
redux中存储的数据会随着刷新浏览器而被重置。redux是基于浏览器内存的存储方式。
持久化:
1.获取时候存储两份,redux里和localStorage里都存储一份
2.redux初始化的时候,优先从localStorage里取,取不到再赋值为空值
处理Token失效
使用Token做路由权限控制(高阶组件)
// 封装高阶组件:高阶组件就是一个函数,接收一个组件为入参,返回一个新的组件
const { getToken } = require("@/utils");
const { Navigate } = require("react-router-dom");
// children:高阶组件包裹的组件
const AuthRoute = ({children}) => {
const token = getToken();
if(token){
return <>{children}</> // 直接return children 也生效
}else{
// Navigate重定向 replace 直接替换,不要记录
return <Navigate to="/" replace />
}
}
export default AuthRoute;
// 使用高阶组件
import { createBrowserRouter } from "react-router-dom";
import Login from "@/pages/Login";
import Layout from "@/pages/Layout";
import AuthRoute from '@/components/AuthRoute';
const router = createBrowserRouter([
{
path: '/',
element: <Login />
},
{
path: '/index',
element: <AuthRoute><Layout/></AuthRoute>
}
])
export default router;
react第七天
useLocation
获取路由相关信息
import { useLocation } from 'react-router-dom';
const location = useLocation();
useReducer
useReducer是useState的替代方案,接收一个reducer和初始值为入参,返回当前数据状态和配套更改数据的dispatch方法。(理解类似于redux)
reducer是一个纯函数,没有任何UI和副作用。输入固定的值,返回固定的值。
import { Button, Space } from "antd";
import { Fragment, useReducer } from "react";
const UseReducerComponent = () => {
// 定义一个reducer函数,根据不同的指令返回不同的状态
const reducer = (state, action) => {
// action 是触发更改时候接收的参数
switch(action.type){
case 'DEC':
return state - 1;
case 'INC':
return state + 1;
case 'SET':
return action.payload;
default:
return state
}
}
// 组件中通过useReducer使用reducer函数,并设置state的初始值。解构出新的状态以及改变状态的方法
const [ state, dispatch ] = useReducer(reducer, 0)
return <Fragment>
{/* 触发不同的指令,根据type来作区分 */}
<Button onClick={()=>dispatch({type:'DEC'})}>-</Button>
<Space>{ state }</Space>
<Button onClick={()=>dispatch({type:'INC'})}>+</Button>
<Button onClick={()=>dispatch({type: 'SET', payload: 10})}>加到10</Button>
</Fragment>
};
export default UseReducerComponent;
React.memo
缓存子组件,只有在子组件的props发生改变的时候,才会重新渲染。
react渲染机制:父组件重新渲染,就会导致子组件重新渲染。浪费性能。
使用方式:memo包裹住子组件
import { memo } from "react";
const Son = (props) => {
console.log('son重新渲染了');
return <div>我是子组件</div>
}
export default memo(Son);
props的比较机制
使用memo缓存组件之后,React会对每一个props的值使用Object.is来比较新值和老值,返回true则表示没有变化
对于基础类型,值不变就不变。
对于引用类型,要保证引用地址不变。父组件重新渲染时候,父组件内的引用类型数据实际上都是新的引用类型了。
对于引用类型,可以使用useMemo来缓存下来,这样父组件每次重新渲染的时候,依赖项不变的话,引用类型也就不会发生变化了
const list = useMemo(()=>{ return [1,2,3] }, []) return <Fragment> <Card> <Son value={list} /> <hr /> <Button onClick={()=>setCount(count+1)}>+</Button> </Card> </Fragment>
axios
get 传参 params
post 传参 data
打包和本地预览
npm run build 打出一个静态包
npm i -g serve 全局安装serve
serve -s build 通过静态服务器模拟生产服务器运行项目的过程
打包优化
- 配置路由懒加载
包体积分析
使用第三方插件:source-map-explorer
1.安装:npm i source-map-explorer
2.配置package.json: "analyze":"source-map-explorer 'build/static/js/*.js'" (指定路径,示例为分析js文件夹下所有的js文件中包的大小占比)
3.启动命令:npm run analyze
4.找出占比较大的包,进行优化处理
CDN优化
CDN:是一种内容分发网络服务,当用户请求网站内容时,由离用户最近的服务器将缓存的内容资源传递给用户
适用于:体积较大的非业务js代码,例如react、react-dom等。
1)体积较大,需要利用CDN文件在浏览器的缓存特性,加快加载时间。
2)非业务js文件,不需要经常变动,CDN不用频繁更新缓存。
项目中如何做?
1)把需要CDN缓存的文件排除在打包之外。
2)以CDN的方式重新引入资源
react第八天
useCallback
在组件多次重新渲染的时候缓存函数
// useCallback里接收一个回调函数,一个依赖项
useCallback(()=>setCount(count+2), [count]);
forwardRef
能够将父组件的ref属性传递给下层组件里使用。
//父组件
import { Button, Card } from "antd";
import { Fragment, useMemo, useRef } from "react";
import SonRef from "./compoennts/SonRef";
const Hooks = () => {
const sonRef = useRef(null);
const getRef = () => {
console.log( sonRef.current );
}
return <Fragment>
<Card>
<h3>forwardRef</h3>
<SonRef ref={sonRef} />
<button onClick={getRef}>获取ref</button>
</Card>
</Fragment>
}
export default Hooks;
//子组件
import { forwardRef, memo } from "react";
// ref 和 props是同级的数据,没嵌套关系
const Son = (props, ref) => {
return <div>
<input ref={ref} />
</div>
}
export default memo(forwardRef(Son));
useInperativeHandle
通过ref暴露子组件中的方法,供父组件来调用
注意点:
- 方法接收两个参数
- ref:来自父组件
- 函数:里包含要暴露出的方法
- 父组件和子组件都要绑定一个ref。
- 父组件的ref属性是找到子组件的
- 子组件的ref属性是找到DOM节点的
react第九天
class的模式
生命周期
- componentWillMount
- componentDidMount
- componentWillReceiveProps
- shouldComponentUpdate
- componentWillUpdate
- componentDidUpdate
- componentWillUnmount
组件间通信
同hook模式下的组件间通信。取props的时候有点差别,hook是通过传参props来获取,class是通过this.props来获取。
zustand--极简状态管理工具
参考文档:www.jianshu.com/p/516c85c50…
react第十天
react 搭配 typescript
useState
import { Fragment, useState } from "react"
const UseStateComponent = () => {
// 定义数据类型
type User = {
name: string | undefined,
age: number
}
const [ count, setCount ] = useState(0);
// 限定user的类型,为 User定义的或者是null
const [ user, setUser ] = useState<User | null>(null);
const add = () => {
setCount(count + 1)
}
const grow = () => {
setUser({
name: user?.name,
age: (user?.age || 0) + 1
})
}
return (<Fragment>
<button onClick={add}>{count}</button>
{/* 类型守卫 */}
<div>{ user?.name }{ user?.age }</div>
<div onClick={grow}>长大</div>
</Fragment>)
}
export default UseStateComponent;
props
要注意children 和 事件 的类型约束方式
事件时候,父组件的两种调用方式,参数会有一些差别。非内联函数要指定参数的类型
// 父组件
<PropsComponent
className="parentClass"
value="parent"
// 绑定props时,如果非内联函数,则需要对参数的类型做注解
// onGetMsg={handleGetMSG}
// 如果是内联函数,则可直接推断出参数类型,不需要额外注解
onGetMsg={(msg, num)=>console.log(msg, num)}
>
<div>我是propsComponent内的内容</div>
</PropsComponent>
// 子组件
// 做注解 用type对象 或者 interface接口都可以
interface Prop{
className: string,
value: string,
// 特殊的参数 children(包裹在组件内的代码)
children: React.ReactNode,
// 事件props,重点在与对函数参数的注解 void指函数没有返回值
onGetMsg?:(msg: string, num: number) => void
// 可选参数
title?: string
}
const propsComponent = (props: Prop) => {
const { className, value, children, onGetMsg } = props;
return <div className={ className }>
父组件传过来的value{ value }
{/* 事件可能为undefined,需要做一个守卫 */}
{/* 组件内调用时候,需要遵守参数类型的约束 */}
<button onClick={()=>onGetMsg?.('btnClick', 10)}>click me</button>
{ children }
</div>
}
export default propsComponent;
useRef
1.获取dom元素
2.引用稳定存储器
import { useRef,useEffect } from 'react';
const UseRefComponent = () => {
// 1.useRef搭配ts:-- 获取DOM
// 把需要获取的dom元素的类型当成泛型参数传递给useRef。可以推导出.current属性的类型
// 如下类型的约束,即 inputRef只能绑定到input元素上
const inputRef = useRef<HTMLInputElement>(null);
useEffect(()=>{
console.log( inputRef.current );
}, [])
// 2.useRef搭配ts -- 引用稳定的存储器
// 泛型传入联合类型
const timerRef = useRef<number|undefined>(undefined);
useEffect(()=>{
// 利用ref的current属性,存储一个计时器
timerRef.current = setInterval(()=>{
console.log(1)
}, 1000);
// 卸载的时候清楚定时器
return ()=>clearInterval(timerRef.current);
}, []);
return <div>
<input ref={inputRef}></input>
</div>
}
export default UseRefComponent;
react+vite 配置别名
1.要安装 types/node -- 搭配ts使用的时候要安装
npm i @types/node -D
2.配置别名--vite.config.js
resolve: {
alias: {
"@": path.resolve(__dirname, './src')
}
}
3.配置路径联想 -- tsconfig.json
{
"baseUrl": ".",
"paths": [
"@/*": [
"src/*"
]
]
}
axios + ts
类型断言:
把一段代码插入到html里:dangerouselySetInnerHTML
<div dangerouslySetInnerHTML={{
__html: detail.content
}}></div>
antd组件使用记录
Form
-
initialValues 设置初始值
-
onFinish :htmlType为submit的按钮点击触发,入参为表单值
-
setFiledsValue: 设置表单的值
// 获得form实例,绑定到form身上,在调用实例的setFiledsValue方法赋值 const [ form ] = Form.useForm(); form.setFiledsValue(data); <Form form={form} > </Form>
知识点补充
1.什么是高阶组件?
高阶组件就是一个函数,他接收一个组件作为参数,返回一个新的组件。
2.useState和useRecuder
react 汇总
钩子函数汇总
参考文档:zhuanlan.zhihu.com/p/378392264
注意:1)只能在组件内使用 2)在组件的顶层引入,不能在if 芙蓉
react提供的钩子函数:
useState
useRef
useContext
useEffect
useMemo
React.memo
React.forwardRef
createContext
Context.Provider组件
react-redux提供的钩子函数:
useSelector
useDispatch
Provider组件
reduxjs/toolkit提供的函数:
createSlice
configureStore
react-router-dom
useNavigate
useLocation
createBrowserRouter
createHashRouter
RouterProvider组件
Link组件
Navigate组件
好用的js库汇总:
day.js:dayjs.fenxianglu.cn/category/#n…
npm install --save dayjs
moment.js: momentjs.cn/
npm install --save moment
import moment from 'moment';
lodash: www.lodashjs.com/
npm install --save lodash
import _ from 'lodash';
classnames: www.npmdoc.org/classnamesz…
npm install --save classnames
import classNames from 'classnames';
<span className={classNames('tabItem', {'activeTab': tabType === 'time'})}>最新
Normalize.css:重置浏览器默认样式
npm install normalize.css
问题记录
1.useCallback里设置状态数据,只能生效一次?
要添加依赖项。
import { Button, Card } from "antd";
import { Fragment, useCallback, useMemo, useState } from "react";
import UseReducerComponent from './compoennts/UseReducerComponent';
import Son from "./compoennts/Son";
const Hooks = () => {
const [ count, setCount ] = useState(0);
const handleSonClick = useCallback(()=>setCount(count+2), [count]);
return <Fragment>
<Card>
<Son
handleClick={ handleSonClick }
/>
<Button onClick={()=>setCount(count+1)}>+</Button>
</Card>
</Fragment>
}
export default Hooks;