当前位置: 首页 > news >正文

第十二步:react

React

1、安装

1、脚手架

  • npm i -g create-react-app:安装react官方脚手架
  • create-react-app 项目名称:初始化项目

2、包简介

  • react:react框架的核心
  • react-dom:react视图渲染核心
  • react-native:构建和渲染App的核心
  • react-scripts:脚手架的webpack配置
  • web-vitals:性能检测工具

3、运行

  • npm run start:运行项目
  • npm run build:打包项目

4、配置简介

  • package.eslintConfig:代码规范配置
  • package.browserslist:浏览器兼容配置
  • package.proxy:配置服务代理

5、文件目录

根据项目需求创建即可,以下为参考建议

- project 项目总文件- public 资源文件- src- api 接口- assets 资源- components 组件- layout 主体- router 路由- store 状态- utils 工具- view 页面- test
  • public:打包时不会进行处理,打包完成后会复制一份到包里
  • src/assets:打包时会压缩,编译等处理。

6、理论

1、react

用于构建 Web 和原生交互界面的库

2、设计
  1. 操作DOM
    • 操作DOM比较消耗性能,可能会导致DOM重绘和回流
    • 操作繁琐,容易出错,效率底,不利于维护
  2. 数据驱动:
    • 通过数据驱动视图,减少DOM操作
    • 框架底层也是操作DOM
      • 构建 虚拟DOM 到 真实DOM 的渲染体系
      • 有效避免DOM重绘和回流
    • 开发效率高,易于维护
3、模式

react采用MVC模式, vue采用MVVM模式

  • MVC模式:Model数据层 + View视图层 + Controller控制器
  • MVVM模式:Model数据层 + View视图层 + ViewModel视图/数据监听层
4、根
  • react通过创造更节点,开始渲染dom
  • const root = ReactDom.createRoot(node)
    • node:获取的节点元素,通常是<div id="root"></div>
    • root:创建的根节点
      • root.render(组件),开始渲染组件
  • 每个组件必须只有一个根标签

7、渲染机制

  1. 把jsx转换为虚拟dom。
  2. 虚拟dom转换为真实dom。
  3. 数据变化,通知Controller,修改数据层。
  4. 数据变化,通过diff算法,计算出视图差异部分(补丁包)。
  5. 把补丁包进行渲染。

  1. 转译:通过babel-preset-react-appjsx转译成React.createElement
  2. 虚拟dom:通过React.createElement创建出虚拟dom
  3. 虚拟dom对象:
    • $$typeof:是否为有效的react元素,Symbol("react.element")
    • ref:允许访问该dom实例
    • key:列表元素的唯一标识
    • type:标签名,或组件名
    • props:接受到的样式、类名、事件、子节点、参数
      • style:样式对象
      • className:类名
      • children:子节点
      • onClick:绑定的点击事件
  4. 真实DOM:通过ReactDom中的render方法,把虚拟dom转换为真实dom

2、样式

1、样式设置

function App() {return (<><div style={{ color: "red" }}>hello world</div><div className="box">react</div></>)
}

2、样式穿透

  • react中,父组件样式文件,能够直接影响子组件

3、样式隔离

  • 样式文件设置为index.module.[css|less|scss|sass]
  • 引入样式import style from "./index.module.css"
  • 使用样式<div className={style.box}>hello world</div>

3、渲染

1、基础渲染

组件渲染、条件渲染、列表渲染、事件响应、数据显示、动态数据

import { useState } from "react";function Box(props) {let [isShow, setIsShow] = useState(false);let arr = ["a", "b", "c"];let content;if (isShow) {content = <div>我是内容</div>;} else {content = null;}return (<dov><button onClick={() => setIsShow(!isShow)}>按钮 - {isShow}</button>{arr.map((item, index) => (<li key={index}>{item}</li>))}{isShow && <div>我是内容</div>}{content}</dov>);
}export default Box;

2、插槽

1、使用
  • 通过props.children能获取子节点。

    function Box(props) {return (<div>插槽-{props.children}</div>)
    }
    
2、具名
  • 通过React.ChildrenApi能够遍历获取每个插槽子节点
  • 通过children.props.slot属性,能够读取子节点自定义的slot
  • 根据slot属性,能够判断每个子节点的渲染位置
3、传参一:
  • 通过props.children获取到的子节点,无法修改它的props

  • 但是能通过React.cloneElement复制一个子节点,然后重新赋值props

    import { cloneElement } from "react";
    function Box() {let child = cloneElement(props.children, {data: "123",...props.children.props});return (<div>{child}</div>);
    }
    
4、传参二:
  • 通过createContextuseContext进行传参

4、传参

1、父传子

  • 父组件可以直接通过属性赋值的方式,把变量和函数传递给子组件
  • 子组件可以通过参数props读取变量和方法,props只读无法修改

2、父读子

import { useRef, useImperativeHandle, forwardRef } from "react";// 父组件
function Fa() {let ref1 = useRef();let ref2 = useRef();function btnClick() {console.log(ref1.current.Tname);console.log(ref1.current.handleT());console.log(ref2.current.Tname);console.log(ref2.current.handleT());}return (<div><button onClick={btnClick}>父按钮</button><Son1 ref={ref1}></Son1><Son2 ref={ref2}></Son2></div>);
}// 子组件一
let Son1 = forwardRef(function (props, ref) {let Tname = "子1";let handleT = () => `我是${Tname}`;useImperativeHandle(ref, () => ({ Tname, handleT }));return <div>Son1</div>;
});// 子组件二
let Son2 = forwardRef(function (props, ref) {let Tname = "子2";let handleT = () => `我是${Tname}`;useImperativeHandle(ref, () => ({ Tname, handleT }));return <div>Son2</div>;
});export default Fa;

3、兄弟传参

  • 借用父组件变量传参
  • 使用 状态管理 传参,最优选
  • 使用createContextuseContext进行传参,优选

5、生命周期

1、类组件

  • constructor:组件加载前
  • render:组件加载
  • componentDidMount:组件加载完成
  • shouldComponentUpdate:数据更新时
  • componentDidUpdate:数据更新完成
  • componentWillUnmount:数据卸载前

2、函数组件

1、执行

函数组件,每次加载前,数据更新时,渲染时,都会执行当前函数组件的函数体。

所以函数组件本身就能监听:加载前、加载、数据更新 三种状态

2、加载完成
  • 使用useEffect能监听组件加载完成

    function App() {useEffect(() => {console.log("组件加载");});return(<div>hello world</div>);
    }
    
3、卸载前
  • 使用useEffect能够监听组件卸载前

    function App() {useEffect(() => {return () => console.log("组件即将卸载");});return(<div>hello world</div>);
    }
    
4、数据更新后
  • 使用useEffect能监听数据的变化,包括props,state

  • 但是加载完成时,也会触发数据监听

    function App() {let [value, setValue] = useState("");let change = (e) => setValue(e.target.value);useEffect(() => {console.log("数据更新");}, [value]);return (<input type="text" onChange={change} value={value} />);
    }
    

5、出入动画

虽然能够监听组件即将卸载这个生命周期

但是由于react组件每次数据更新,都会重新执行当前函数,就会导致执行到隐藏条件判断时,不对动画节点进行虚拟dom构造,也就导致dom树节点没有动画的节点。

所以,进入动画能够很方便的完成,消失动画无法正常完成。


所以需要利用数据更新后,每次执行函数体的特征,指定3种状态。

0 隐藏,1 显示,-1 消失,消失动画完成后,再切换为0

  1. 初始时,状态为0,className为空字符
  2. 点击显示,状态变成1,className变显示动画类
  3. 点击消失,状态变成-1,className变消失动画类
  4. 消失动画完成后,状态变成0,className变空
function App() {let [show, setShow] = useState(0);// 使用useMemo,避免其他变量变化时,影响到cNamelet cName = useMemo(() => {return {1: "animate__backInDown",[-1]: "animate__backOutDown",}[show];}, [show]);return (<div><button onClick={() => setShow(show ? -1 : 1)}>按钮</button>{show ? (<divclassName={`animate__animated ${cName}`}onAnimationEnd={() => show === -1 && setShow(0)}/>) : null}</div>);
}

6、redux

1、安装

  • redux:核心包
  • @reduxjs/toolkit:新的创建方式

2、api

  • import { createStore } from "redux"
  • const store = createStore(reducer, init):初始化数据对象
    • store:数据对象
      • getState():获取数据
      • dispatch(action):修改数据
        • action:传递的动作对象
      • subscribe(fn):变化订阅函数
        • fn:数据变化时,执行
        • 返回一个函数,执行后取消变化订阅,fn将不再执行
    • reducer:操作函数
      • 参数一(state):修改前的数据
      • 参数二(action):dispatch传递的动作对象
    • init:初始化的数据

3、使用

  • 外部定义变量

    import { createStore } from "redux";const data = { count: 1 };
    function reducer(state, action) {if (action.type in state) state[action.type] = action.value;return { ...state };
    }// 教程视频都是用switch判断action的type,然后执行逻辑。
    // 如果项目设计时,在redux写逻辑,就用switch
    // 如果项目设计时,就只是用redux进行状态管理,就直接修改值// 和useReducer完全一样
    export default createStore(reducer, init);
    
  • 组件内使用

    import { useState, useEffect } from "react";
    import store from "@/store/index";function App() {const data = store.getState();const [count, setCount] = useState(data.count);function changeCount() {store.dispatch({ type: "count", value: count + 1 });}// 订阅store变化const callback = store.subscribe(() => {setCount(store.getState().count);});// 组件卸载时,取消订阅useEffect(() => callback);return (<><div>Count: {count}</div><button onClick={changeCount}>按钮</button></>)
    }
    

4、模块化一

  • 使用combineReducers 合并
import { createStore, combineReducers } from "redux";const counter = { count: 1 };
const sumer = { sum: 10 };
function reducer(data) {return (state = data, action) => {if (action.type in state) state[action.type] = action.value;return { ...state };}
}const reducers = combineReducers({counter: reducer(counter),sumer: reducer(sumer),
})const store(reducers);// 读取值
const counterData = store.getState().counter;
const sumerData = store.getState().sumer;// 其他的没有变化

5、模块化二

  • 首先:没有任何文档说明,store只能创建一个。
  • 使用上下文,能够更好的管理模块数据,也方便使用
import { createStore } from "redux";
import { createContext, useContext } from "react";function reducer(data) {return (state = data, action) => {if (action.type in state) state[action.type] = action.value;return { ...state };}
}const counter = createStore(reducer, { count: 1 });
const sumer = createStore(reducer, { sum: 10 });
const store = createContext({ counter,sumer });export default function useStore() {return useContext(store);
}

6、toolkit-定义

  • const model = createSlice(optons):创建一个store模块
    • model:store模块
      • model.reducer:模块的reducer
      • model.actions:模块的方法对象
    • options:模块配置
      • name:模块名称
      • initialState:初始化值
      • reducers:reducer函数对象
        • reducer对象的方法名,就是model.actions对象的方法名
        • reducer方法
          • 参数一:修改前的state值
          • 参数二:一个对象
            • payload:对应的model.actions对象方法传递的参数
  • const store = configureStore(options):创建一个store
    • store:创建的状态管理
    • options:配置
      • reducer:
        • 直接设为model,就只有一个store模块,没有名称
        • 设对象时,name: model,多个模块
      • middleware:中间件列表

7、toolkit-使用

  • 定义数据

    import { createSlice, configureStore } from "@reduxjs/toolkit";const counter = createSlice({name: "counter",initialState: 0,reducers: {add(state, action) {return state + action.payload;},},
    });export const { add } = counter.actions;const store = configureStore({reducer: counter.reducer,// reducer: { counter: counter.reducer }
    });export default store;
  • 应用

    import { useState, useEffect } from "react";
    import store, { add } from "@/stores/index";function App() {const [count, setCount] = useState(store.getState());function countChange() {store.dispatch(add(1));}let callback = store.subscribe(() => {setCount(store.getState());});useEffect(() => callback);return (<><h1>Count: {count}</h1><button onClick={countChange}>按钮</button></>);
    }

8、tookit-模块化

  • configureStore.reducer配置为对象
  • 组件使用store.getState().model获取值
  • 其他不变

9、持久化

import { createStore } from "redux";let counter = {count: sessionStorage.getItem("count") || 0,
};
function reducer(state, action) {if (action.type in state) {state[action.type] = action[action.type];sessionStorage.setItem(action.type, state[action.type]);}return { ...state };
}const store = createStore(reducer, counter);
export default store;

10、组件外使用

需求:部分业务逻辑可能会在组件外部对数据进行修改

如:接口拦截,统一获取登录状态

  1. 需要在外部使用story.dispatch方法修改数据
  2. 封装统一请求方法,然后请求拦截时,获取登录状态,修改登录状态
  3. 请求时有组件发起的
    • 通过事件响应触发接口请求
    • 通过useEffect监听组件挂载成功,触发接口请求
import { useEffect } from "react";
import store from "@/store/index";
function axios() {setTimeout(() => {sumStore.dispatch({ type: "isLogin", value: true });}, 3000);
}function App() {const { isLogin } = store.getState();useEffect(() => {// 模拟接口请求,修改登录状态axios();});return (<div>{ isLogin ? "登录中" : "未登录" }  </div>)
}

7、mobx

灵活,体积小,适合快速配置

1、安装

  • 下载插件:npm i mobx
  • 下载插件:npm i mobx-react-lite:体积小,支支持函数组件
  • 下载插件:npm i mobx-react:体积大,支持函数组件,类组件

2、数据定义

import { makeObservable, observable, computed, action, flow } from "mobx";class Count {count = 0; // 定义静态属性constructor() {makeObservable(this, {count: observable,double: computed,add: action,api: flow,});}get double() {return this.count * 2;}add() {this.count = this.count + 1;}*api() {let res = yield Promise.resolve(1);this.count = res;}
}const count = new Count();
export default count;
  • makeObservable:在构造函数中,定义哪些属性,方法是可观察的
    • 参数一:当前类的指向
    • 参数二:指定属性,方法
  • observable:定义哪些值为数据值
  • computed:定义哪些值为计算值
  • action:定义哪些值为方法
  • action.bound:定义哪些值为方法,并且强制this执行为当前类
  • flow:定义哪些值为迭代方法
  • 只能绑定静态属性,动态属性无法绑定。

  • makeAutoObservable:自动把属性和方法进行绑定

    • 参数一:当前类的指向

    • 参数二:可选,排除哪些属性,或方法

      如:{ reset: flase },reset方法排除可观测

    • 参数三:可选

      • autoBind:是否自动把this指向绑定到当前类

3、数据使用

import count from "./count";
import { observer } from "mobx-react-lite";function App() {return (<><div>{count.count}</div><div>{count.double}</div><button onClick={() => count.add()}>加一</button><button onClick={() => count.api()}>请求</button></>);
}// 通过高阶函数observer处理
export default observer(App);

4、生成器

mobx通过flow定义哪些属性可以通过生成器进行处理

*api() {const res = yield Promise.resolve(2);const res2 = yield Promise.resolve(2 + res);this.count = res;
}

执行过程:

  1. 通过调用api,获取一个generator对象,这个对象是个可迭代对象(iterator)。
  2. 第一次next,
    • 会执行代码到第一个yield。然后把第一个yield后面的结果返回
    • 同时会把next传递的参数传递给res
  3. 依次类推,每次next都是如此执行。
  4. 通过for-of,能够遍历执行
  5. 所以flow就是通过generator生成器,获取请求结果。

注意:flow可以通过yield获取其他值,但是推荐获取Promise对象

5、指针

  • 通过action定义的方法,可以给外界使用,但是this并不一定会指向store
  • 通过action.bound定义的方法,就会把this强制指向到当前store

6、数据监听

  • autorun:监听数据变化
    • 回调函数:
      • 如果回调函数内没有任何属性数据,只会监听初始化
      • 如果回调函数内有属性的数据,就会监听属性数据的变化
      • 属性数据可以有多个,监听就会同时进行
  • reaction:只监听store内的某一个数据是否发生变化
    • 参数一:回调函数,需要返回观察属性
    • 参数二:观察属性发送变化,才会执行
    • 不会监听初始化
import { makeAutoObservable, autorun, reaction } from "mobx";class Count {count = 0;sum = 0;constructor() {makeAutoObservable(this, {}, {autoBind: true});}addCount() {this.count++;}addSum() {this.sum++;}
}const count = new Count();// 只监听初始化
// autorun(() => {
//   console.log("只监听初始化");
// });// 只监听sum
// autorun(() => {
//   console.log("监听sum变化", count.sum);
// });// 监听count, sum
// autorun(() => {
//   console.log("监听sum和count变化", count.sum, count.count);
// });reaction(() => count.count,(c) => console.log("count变化", c);
)

7、异步

  • action:定义的方法可以直接使用异步,但是会进行警告。

  • runInAction:让方法中可以异步修改属性

    import { makeAutoObservable, runInAction } from "mobx";
    class Count {value=0;constructor() {makeAutoObservable(this, {}, {autoBind: true});}add() {setTimeout(() => {runInAction(() => {this.value++;});}, 1000);}
    }let count = new Count();
    export default count;
    

8、模块化

通过useContext进行跨组件通信

import { createContext, useContext } from "react";
import count from "./count";
import sum from "./sum";const countext = createCountext({ count, sum });
export function useStore() {return useContext(countext);
}

组件内使用

import { useStore } from "./store/index";
import { observer } from "mobx-react-lite";function App() {let { count, sum } = useStore();return (<div><span>{ count.value }</span><span>{ sum.value }</span></div>)
}export default observe(App);

9、持久化

通过sessionStorage轻松完成数据持久化

import { makeAutoObservable, autorun } from "mobx";class Count {value = sessionStorage.getItem("count") || 0;constructor() {makeAutoObservable(this);}add() {this.value++;}
}let count = new Count();
autorun(() => {sessionStorage.setItem("count", count.value);
});export default count;

10、组件外使用

需求:部分业务逻辑可能会在组件外部对数据进行修改

如:接口拦截,统一获取登录状态

  1. 需要在外部使用mobx订阅的方法修改数据
  2. 封装统一请求方法,然后请求拦截时,获取登录状态,修改登录状态
  3. 请求时有组件发起的
    • 通过事件响应触发接口请求
    • 通过useEffect监听组件挂载成功,触发接口请求
import { useEffect } from "react";
import { observer } from "mobx-react-lite";
import store from "@/store/index.js";
function axios() {setTimeout(() => {store.setIsLogin(true);}, 3000);
}function App() {useEffect(() => {// 模拟接口请求,修改登录状态axios();});return (<div>{ store.isLogin ? "登录中" : "未登录" }  </div>)
}export default observer(App);

8、路由

什么是路由?请求接口的地址是路由,网页的地址也是路由。

所以,网页的路由就是通过不同的GET请求地址,获取不同的页面。

1、安装

  • react-router-dom:路由插件,版本6+

2、标签导航

1、路由定义
  • BrowserRouter:定义history路由模式
  • HashRouter:定义hash理由模式
  • Routes:定义路由页面
  • Route:定义路由页面与匹配路径
    • index: 是否为默认路由,为路由索引头部,不能给索引添加子路由
    • path:定义路由地址
    • element:定义路由元素
    • Component:定义路由组件元素
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Mine from "./mine";function App() {return <BrowserRouter><h1>app</h1><Routes><Route index element={<div>首页</div>}></Route><Route path="mine" Component={<Mine />}></Route></Routes></BrowserRouter>
}
2、路由跳转
  • Link:路由跳转标签
    • to:路由跳转地址
  • NavLink:路由跳转标签
    • to:路由跳转地址
import { BrowserRouter, Link, NavLink } from "react-router-dom";function App() {return <BrowserRouter><h1>app</h1><Link to="home">首页</Link><NavLink to="mine">我的</NavLink></BrowserRouter>
}
3、路由重定向
  • 修改默认路由指向路径

  • 重定向路由指向路径

  • Navigate:路由重定向,普通组件,不是路由组件

    • to:目标路由
    import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
    import Mine from "./mine";function App() {return <BrowserRouter><h1>app</h1><Routes><Route path="home" element={<div>首页</div>}></Route><Route path="mine" element={<Mine />}></Route><Route index element={<Navigate to="home" />}></Route></Routes></BrowserRouter>
    }
    
4、子路由
  • Outlet:嵌套路由插槽

  • 多级路由:

    • <Route path="first/second" element={<div>second</div>} />

    • 一个路由地址对应一个标签或组件

  • 嵌套路由:

    • 一级路由对应页面A
    • 二级路由对应页面B
    • 页面B嵌套再页面A中,页面B是页面A的子页面
    import { BrowserRouter as Router, Routes, Route, NavLink,Outlet, Navigate } from "react-router-dom";function Box() {return (<div><h3>box</h3><Outlet /></div>);
    }function App() {return (<Router><h1>app</h1><NavLink to="home">首页</NavLink><NavLink to="home/about">关于</NavLink><Routes><Route path="home" element={<Box />}><Route path="info" element={<div>信息</div>}></Route><Route path="about" element={<div>关于</div>}></Route><Route index element={<Navigate to="info" />}></Route></Route><Route index element={<Navigate to="home" />}></Route></Routes></Router>);
    }
    

3、编程导航

1、路由定义
  • createBrowserRouter:定义history路由

  • createHashRouter:定义hash路由

  • RouterProvider:定义路由页面

    • router:使用创建的路由
  • routerListItem:路由配置

    • path:路由路径
    • element:路由页面
    • Component:路由组件,可以为函数,可以为组件标签
    • index:是否为默认路由,为路由索引头部,不能给索引添加子路由
    • meta:路由元信息,值为对象
    • children:子路由列表
    import { createBrowserRouter, RouterProvider } from "react-router-dom";const routers = createBrowserRouter([{ path: "home", element: <div>home</div>, index: true },{path: "mine",element: <Mine />,meta: { icon: "mine" },children: [{ path: "info",element: <div>mine info</div>,index: true,}]}
    ]);function App() {return <div><h1>app</h1><RouterProvider router={routers} /></div>;
    }
    
2、路由跳转
  • const navigate = useNavigate():返回路由跳转函数
    • navigate(参数):进行路由跳转
      • 字符串参数:进行路由路径跳转
      • -1:向后跳转
      • 1:向前跳转
  • 注意:只能在存在Router上下文的组件中使用
3、路由重定向
  • createBrowserRouter的路由配置项中,并没有路由重定向配置

  • 只能用Navigate进行路由重定向

    const routers = createBrowserRouter([{ path: "/", index: true, element: <Navigate to="home" /> },{ path: "home", element: <div>首页</div> }
    ]);
    
4、子路由
  • 多级路由:正常配置
  • 嵌套路由:
    • 通过children进行路由配置
    • 被嵌套页面通过Outlet组件进行接受
5、最佳实践
  • src/router/index.js:定义路由配置列表
  • src/index.js
    • 导入路由列表
    • 使用createBrowserRouter创建路由
    • 使用RouterProvider挂载路由
  • src/view/app.jsx:使用Outlet:挂载子路由

4、路由应用

1、路由传值
  • 注意:只能在存在Router上下文的组件中使用
  • 路由传值,也就是get请求传参。
    • params:url/:id,需要对路由路径进行修改
    • query:url?a=1&b=2
    • hash:url#123
  • 读取传参:
    • const params = useParams(); 读取params传值
    • const [querys] = useSearchParams(); 读取query传值
      • query.get(key):读取key的值
      • query.append(key, value):新增
      • query.delete(key):删除
      • query.set(key, value):修改
      • query.has(key):判断是否存在
      • query.keys()
      • query.values()
      • query.forEach(fn)
      • query.toString():输出字符串
      • query.size:个数
    • const location = useLocation():读取当前路由对象
      • location.hash:读取hash传值
      • location.meta:读取路由元信息
      • location.pathname:读取路由地址
      • location.search:读取query传值
2、路由懒加载
  • react-router-dom:没有路由懒加载功能

  • 使用React.lazy高阶函数,能够实现路由组件懒加载

    const routers = createBrowserRouter([{ path: "/", index: true, element: <Navigate to="home" /> },{path: "/home",Component: React.lazy(() => import("@/view/home"))}
    ]);
    
3、跳转前拦截
  • react-router-dom没有路由跳转前拦截
  • 只能在navigate调用前,手动执行拦截逻辑
4、跳转后通知
  • react-router-dom没有没有路由跳转后拦截
  • 只能通过监听useLocation获取的loaction变化,判断是否跳转完成
  • 使用useEffect监听
5、路由封装
  1. 路由设置封装

    • 设计思想:文件驱动路由
    • 通过动态读取view文件目录,生成路由配置
    // src/route/index.js
    import { lazy } from "react";
    import { createBrowserRouter, Navigate } from "react-router-dom";const baseUrl = "view"; // 配置读取目标
    const root = "app.jsx"; // 配置layout根节点
    const indexUrl = "home"; // 配置默认路由
    const error = "error.jsx"; // 配置404const routes = [{ path: "/", Component: lazy(() => import(`@/${baseUrl}/${root}`)) },{ path: "*", Component: lazy(() => import(`@/${baseUrl}/${error}`)) },
    ];
    const children = [{ index: true, element: <Navigate to={indexUrl} /> }];
    const files = require.context("@/view", true, /index\.jsx$/);
    files.keys().forEach((file) => {const model = lazy(() => import(`@/${baseUrl}${file.slice(1)}`));const segments = file.split("/");let current = {};for (let i = 1; i < segments.length; i++) {const segment = segments[i];if (segment === "index.jsx") {current.Component = model;} else {let list = children;if (i !== 1) {if (!current.children) current.children = [];list = current.children;}const child = list.find((child) => child.path === segment);if (child) current = child;else {current = { path: segment };list.push(current);}}}
    });
    routes[0].children = children;export default createBrowserRouter(routes);
  2. 路由使用封装

    • 根据跳转前拦截,和跳转后通知逻辑进行封装
    • 封装自定义hook:useRoute
    • 返回:{navigate,loaction,beforeRouter,afterRouter,Outlet}
      • navigate(path,params)
        • path:跳转路径
        • params:可选,query传参
      • location:路由信息
      • beforeRouter(callback):跳转前拦截,订阅函数
        • callback:回调函数
          • 参数一:to,路由跳转目标
          • 参数二:from,路由原地址
          • 参数三:next([path]),通过函数,可修改跳转路径
      • afterRouter(callback):跳转后通知,订阅函数
        • callback:回调函数
          • 参数一:to,路由跳转目标
          • 参数二:from,路由原地址
      • Outlet:子路由挂载组件
    // src/router/hook.js
    import { useEffect, useRef } from "react";
    import { useNavigate, Outlet, useLocation } from "react-router-dom";function useRoute() {const befores = new Set();const afters = new Set();const navigateTo = useNavigate();const location = useLocation();const from = useRef(location.pathname);useEffect(() => {afters.forEach((callback) => callback(location.pathname, from.current));from.current = location.pathname;return () => {befores.clear();afters.clear();};// eslint-disable-next-line react-hooks/exhaustive-deps}, [location]);function navigate(path, params) {let query = "";if (typeof to === "string" && params) query = switchParams(params);if (!befores.size) navigateGo(path + query);else {let pass = [];let url = path + query;befores.forEach((callback) => {let promise = new Promise((resolve) => {callback(path.split("?")[0], from.current, (r) => {if(r) url = r;resolve()});});pass.push(promise);});Promise.all(pass).then(() => {navigateGo(url);});}}function switchParams(params) {return new URLSearchParams(params).toString();}function navigateGo(path) {navigateTo(path);}function beforeRouter(callback) {if (typeof callback === "function") {befores.add(callback);return () => {befores.delete(callback);};}}function afterRouter(callback) {if (typeof callback === "function") {afters.add(callback);return () => {afters.delete(callback);};}}return { navigate, location, beforeRouter, afterRouter, Outlet };
    }export default useRoute;
6、路由进度条
  • 路由进度条,就是在body上面添加一个定位元素,然后控制宽度的变化。
  • 路由跳转前,创建元素,并使其宽度变化定值,模拟路由进度
  • 路由跳转后
    • 如果没有元素,创建元素
    • 如果有元素,执行元素动画
    • 元素动画宽度变化为100%,填充body,然后删除元素
  • 使用gsap完成进度动画

  • 封装useRouteProgress,配合useRoute进行使用

  • const progress = useRouteProgress(time)

    • time:动画时间,默认0.2秒

    • progress.start(n):开启进度条,默认动画宽度30%

    • progress.end():完成进度条,并删除进度条

// src/router/progress.js
import gsap from "gsap";
import { useRef } from "react";function useRouteProgress(time=0.2) {let ref = useRef();let timer = useRef();function createDom() {cleanDom();ref.current = document.createElement("div");ref.current.className = "progress";document.body.appendChild(ref.current);timer.current = gsap.timeline({ duration: time });timer.current.set(ref.current, {position: "absolute",top: 0,left: 0,width: 0,height: 2,background:"linear-gradient(90deg,#FFFF00 0%,#DE7474 49.26%,#EE82EE 100%)",zIndex: 9999,});}function cleanDom() {if (ref.current) {document.body.removeChild(ref.current);ref.current.remove();timer.current.kill();ref.current = null;timer.current = null;}}function start(n = 30) {createDom();timer.current.to(ref.current, { width: `${n}%` });}function end() {if (!ref.current) createDom();timer.current.to(ref.current, { width: "100%" }).then(() => cleanDom());}return { start, end };
}export default useRouteProgress;
7、进出动画
  • 出入动画:通过路由跳转前后拦截,进行layout层的动画
  • 路由跳转前:
    • 开启消失动画,控制layout元素在显示器消失
    • 动画完成后,才执行路由跳转功能
  • 路由跳转后:开启进入动画,控制layout元素在显示器出现
  • 使用gsap完成进出动画
  • 封装useRouteAnimate,配合useRoute进行使用
  • const routeAnimate = useAnimate(ref, time);
    • ref:通过ref获取到的layout元素,建议获取h5纯标签元素
    • time:动画时间,默认0.2秒
    • routeAnimate:动画控制器
      • onEnter():进入动画
      • onLeave(callback):消失动画
        • callback消失动画完成回调
// src/router/animate.js
import gsap from "gsap";
import { useRef } from "react";function useRouteAnimate(ref, time = 0.5) {const timer = useRef();function createTimer() {clearTimer();timer.current = gsap.timeline({ duration: time });}function clearTimer() {if (timer.current) {timer.current.kill();timer.current = null;}}function onEnter() {createTimer();timer.current.fromTo(ref.current,{ x: -20, opacity: 0 },{ x: 0, opacity: 1 });}function onLeave(callback) {if (timer.current && ref.current) {timer.current.to(ref.current, { x: 20, opacity: 0 }).then(() => callback());}}return { onEnter, onLeave };
}export default useRouteAnimate;
8、使用案例

入口组件使用

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";
import { RouterProvider } from "react-router-dom";
import "animate.css";
import "./index.scss";
import routers from "./router";const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<><RouterProvider router={routers} /></>
);

layout组件使用

// src/view/app.js
import "./app.scss";
import { useRef } from "react";
import { useRoute, useRouteProgress, useRouteAnimate } from "@/router/hook";function App() {const ref = useRef();const { navigate, Outlet, beforeRouter, afterRouter } = useRoute();const progress = useRouteProgress();const routeAnimate = useRouteAnimate(ref);beforeRouter((to, from, next) => {routeAnimate.onLeave(() => {progress.start();next();});});afterRouter((to, from) => {progress.end();routeAnimate.onEnter();});function handleClick(path) {navigate(path);}return (<div className="wrap"><div className="nav"><button onClick={() => handleClick("home")}>首页</button><button onClick={() => handleClick("mine?a=10")}>我的</button><button onClick={() => handleClick("config/list")}>配置列表</button><button onClick={() => handleClick("config/detail")}>配置详情</button></div><div className="box" ref={ref}><Outlet /></div></div>);
}export default App;
9、登录判断
  1. 通过useRoute,可知:beforeRouter可订阅多个路由跳转前拦截
  2. 通过路由跳转前,获取redux或者mobx管理的是否登录状态,判断是否重定向到登录
  3. 通过接口请求拦截时,可以修改redux或者mobx存储的登录状态

  • 由于目前,前后端大部分都用token无状态登录验证
  • 所以后端会传给前端token,
    • 可以后端存储在浏览器的cookie中
    • 也可以前端获取后,存储在浏览器的cookie或者locationStorage、sessionStorage
  • 但是否登录,单靠前端无法进行有效判断。
  • 需要后端通过接口请求返回登录状态,前端才能进行判断。
  • 返回的token,后端存储在cookie中,并设置响应头HttpOnly,前端无法通过JavaScript访问。

9、高阶组件

  • 高阶组件:通过对组件进行预处理的函数,函数返回一个组件
  • 定义高阶组件:withApp
  • 参数:函数组件
  • 返回:返回一个函数callback
    • callback是一个函数组件
function withApp(Component) {return (props) => {useEffect(() => {console.log("App 加载完成");});return <Component />;}
}function App() {return <div>App</div>;
}export default withApp(App);

10、请求

1、fetch封装

class Api {constructor(baseurl, timeOut) {this.baseurl = baseurl;this.timeOut = timeOut || 10000;}async #request(url, method = "GET", data, json = false, fileName) {const path = this.baseurl + url;const controller = new AbortController();const config = { method, signal: controller.signal };const timeoutPromise = new Promise((_, reject) =>setTimeout(() => {controller.abort();reject(new Error("请求超时"));}, this.timeOut));if (data) {config.body = json ? JSON.stringify(data) : data;if (json) config.headers = { "Content-Type": "application/json" };}try {const res = await Promise.race([fetch(path, config), timeoutPromise]);if (!res.ok) throw new Error(res.statusText);// 进行接口响应后拦截逻辑 - 可通过响应头获取登录状态等const contentType = res.headers.get("content-type").split(";")[0].trim();if (!contentType) throw new Error("Unknown content type");// 处理文件下载if (fileName) {const resData = await res.arrayBuffer();this.#downloadFile(resData, contentType, fileName);return { success: true };}// 返回请求结果return contentType === "application/json"? await res.json(): await res.text();} catch (error) {throw new Error(`请求失败: ${error.message}`);}}#downloadFile(res, contentType, fileName) {const blob = new Blob([res], { type: contentType });const url = URL.createObjectURL(blob);const a = document.createElement("a");a.href = url;a.download = fileName;a.click();URL.revokeObjectURL(url);a.remove();}get(url, query, param) {let path = url;if (query) path += "?" + new URLSearchParams(query).toString();if (param) path += "/" + param;return this.#request(path);}post(url, data) {return this.#request(url, "POST", data, true);}postByFormData(url, data) {let formData = new FormData();for (const key in data) {formData.append(key, data[key]);}return this.#request(url, "POST", formData);}download(url, fileName = "file") {this.#request(url, "GET", null, false, fileName);}upload(url, file, key = "file") {const formData = new FormData();formData.append(key, file);return this.#request(url, "POST", formData);}uploads(url, files, key = "files") {const formData = new FormData();for (const file of files) {formData.append(key, file);}return this.#request(url, "POST", formData);}
}let baseurl = process.env.NODE_ENV === "production" ? "" : "/api/v1";
let api = new Api(baseurl);
export default api;

2、定义接口

  • src/api文件夹下,创建js文件,并定义接口

    import api from "../utils/api";export const getApi = (params) => api.get("/getApi", params);export const postApi = (params) => api.post("/postApi", params);
    

11、服务代理

脚手架create-react-app的代理配置

  • 修改端口:通过在.env文件内修改POST,修改端口号

  • 服务代理:

    1. 方式一:

      • 通过直接在package.json中,添加proxy字段,进行代理
      • "proxy": "http://localhost:8080"
        • 方便快捷,直接设置
        • 只能配置字符串,只能代理一个服务,无法修改前缀
    2. 方式二:

      • 下载插件:npm i -D http-proxy-middleware

      • 通过在src下创建setupProxy.js配置代理

      • 脚手架会自动寻找src/setupProxy.js,然后执行代理配置

      • 注意:setupProxy.js不能使用ESM导入导出

        const { createProxyMiddleware } = require("http-proxy-middleware");module.exports = function (app) {app.use("/api",createProxyMiddleware({target: "http://localhost:8080",changeOrigin: true,pathRewrite: { "^/api": "" },}));
        };
        • 配置灵活,能够代理多个服务,可以修改前缀

12、环境变量

react最新的脚手架,也能使用.env环境变量文件

  • .env:通用环境变量文件

  • .env.development:开发读取

  • .env.test:测试读取

  • .env.production:生成读取

  • 由于脚手架的设置,环境变量名必须以REACT_APP_开头

    如: REACT_APP_MYCODE = abcdef

  • 环境变量文件修改,不会触发热更新


  • 代码中,通过process.env.*读取环境变量
  • process:nodejs中进程模块
  • process.env.NODE_ENV:脚手架自动设置的环境变量,值为:
    • development:开发环境
    • production:生成环境
    • test:测试环境

13、配置别名

  • 下载插件npm i -D @craco/craco

  • 修改启动项

    "scripts": {"dev": "craco start","build": "craco build","test": "craco test","eject": "react-scripts eject"
    },
    
  • 新增craco.config.js文件

    const path = require("path");module.exports = {webpack: {alias: {"@": path.resolve(__dirname, "src"),},},
    };
    
  • 新增jsconfig.json文件

    {"compilerOptions": {"baseUrl": ".","paths": {"@/*": ["src/*"]}},"include": ["src"]
    }
    

  • 可以发现,使用@craco/craco插件,会修改react-scripts的默认配置
  • 所有:完全可以不需要使用npm run eject,抛出默认配置
  • 只需要使用@craco/craco插件,对webpack配置进行微调

14、静态资源

  • public文件夹:不会被编译,压缩。打包时会复制内容到dist包目录
  • src/assets文件夹:打包时会被编译,压缩

  • src连接使用
    • public
      • <img src="/imgs/bg.png">
      • 直接使用public为根路径,然后使用文件地址
    • src/assets
      • import bg from "@/assets/imgs/bg.png"
      • <img src={bg} />
      • 需要使用ESM引入图片,然后复制给图片src

  • css使用:正常使用路径引入
    • public
      • background: url("../../public/imgs/bg.png");
    • src/assets
      • background: url("../assets/imgs/bg.png");

15、Hooks

1、useState

  • 创建参与渲染有关的变量
  • let [data, setData] = useState(0)
    • 参数:初始化的值
    • 返回数组
      • item1:参与渲染的变量
      • item2:修改变量的函数
  • 每次修改变量,都会刷新组件。

2、useEffect

  • 监听函数组件挂载,卸载
  • 监听函数组件内,动态数据的变化

3、useRef

1、记忆

希望能够像useState一样能够记录一个值,但又不想参与渲染,如定时器

const ref = useRef(null)

2、获取dom
  • 通过定义ref,获取一个不参与渲染的变量。
  • 通过props赋值,把ref赋值给子节点
import { useRef, forwardRef, useImperativeHandle } from "react";function App() {let ref1 = useRef(null);let ref2 = useRef(null);let ref3 = useRef(null);function handleClick() {console.log(ref1.current);console.log(ref2.current);console.log(ref3.current);}return (<><h1 ref={ref3}>根</h1><button onClick={handleClick}>按钮</button><Soned ref={ref1} /><Soned ref={ref2} /></>);
}function Son(props, ref) {useImperativeHandle(ref, () => ({name: "son",}));return (<><h3>Son</h3>  </>)
}const Soned = forwardRef(Son);
  • 普通标签,可以直接通过ref获取到元素。
  • 自定义组件
    • 首先需要forwardRef把ref注入到函数组件的第二个参数中
    • 然后需要使用useImperativeHandle定义哪些属性暴漏给ref

4、useImperativeHandle

  • 定义哪些属性暴漏给ref
  • 参数一:接受到的ref
  • 参数二:回调函数,返回暴漏的值

5、useCoutext

  • const MyContext = createContext(defaultValue):创建一个上下文对象

    • defaultValue:设置默认值
  • 通过MyContext.Provider包裹子组件,通过value设置值

    <MyContext.Provider value={{ data }}>{children}</MyContext.Provider>

  • 通过useContext(MyContext):读取上下文对象

    const { data } = useContext(MyContext);

6、useReduce

  • const [state, dispatch] = useReduce(reducer, init)
    • state:显示的数据
    • dispatch:修改函数
    • init:初始化的默认值
    • reducer:修改函数
      • state:原有的state值
      • action:dispatch传递的参数
      • 必须进行返回,返回的值会覆盖原有的state
import { useReducer } from "react";const init = { title: "abc" };const reducer = (state, action) {if ( action.type in state ) {state[action.type] = action.value;}return { ...state };
}function App() {let [state, dispatch] = useReducer(reducer, init);function handleClick() {dispatch({ type: "title", value: "xdw" });}return (<><h3>{state.title}</h3><button onClick={handleClick}>按钮</button></>)
}

7、useMemo

  • 如果组件内,有动态时间显示,那么这个组件就会每秒就进行刷新
  • 如果这个组件内同时存在一个依赖与另一个值的大量计算,那么每次刷新都会重新大量计算
  • 所以就出现需求:某个计算值,不会受其他的state变化印象的需求
  • let data = useMemo(work, [dependencies])
    • data:work函数执行后返回的值
    • work:执行函数,必须有返回值
    • dependencies:监听的states
  • 首次渲染时会执行
  • 只有在监听的states变化时,才会执行work,data才会变化

  • 可以充当计算属性使用,拥有缓存的效果
  • 可以避免大量重复性计算,提高性能

如果不使用useMemo、如何解决?

  • 进行状态降级,就是把功能细分后,变成更小的组件。
  • 让两个组件不会相会影响

8、useCallback

useMemo保证了值的不变性,useCallback就保证了函数的不变性

  • 通过useMemomemo可知:
  • 如果传递的props是引用类型数据,子节点还是会被刷新。
  • 所以需要useMemo处理传递的引用类型数据。
  • 如果传递是函数,就可以使用useCallback

  • const fn = useCallback(work, [dependencies])
    • fn:就是work函数
    • work:需要处理的函数
    • dependencies:监听的states
  • 可以理解为useCallbackuseMemo的降级处理。
  • useMemo会调用work,然后获得返回值
  • useCallback不会调用函数,而是把调用职权弹出

9、useLayoutEffect

  • useEffect:是监听组件渲染完成后执行
  • uesLayoutEffect:是监听组件渲染前执行
    • 使用 方式 和useEffect完全一样。
    • 例如:动态设置元素的高度,让元素高度超过父元素时隐藏。
      • 此时就可以通过useLayoutEffect在浏览器渲染组件前获取到高度
      • 然后执行判断逻辑,动态设置高度。
      • 然后再进行渲染
    • 所以useLayoutEffect会阻塞组件渲染,非必要不要使用
    • 会造成页面卡顿

10、useDeferredValue

用于延迟state的变化。

在处理大量数据时,或者优先显示时很有用。

import { useState, useDeferredValue } from "react";function App() {const [query, setQuery] = useState('');const deferredQuery = useDeferredValue(query);return (<div><inputtype="text"value={query}onChange={(e) => setQuery(e.target.value)}/><div>延迟显示:{deferredQuery}</div></div>);
}

11、useTransition

让setState变成非紧急处理,让其他的setState优先变化,渲染。

如果state变化时间过长,希望监听state是否变化完成。

  1. 可以通过useEffect监听数据的变化
  2. 可以通过useTransition监听事件是否变化完成
function App() {const [query, setQuery] = useState("");const [isPadding, startTransition] = useTransition();function handleChange(e) {startTransition(() => {setQuery(e.target.value);});}return (<div><input type="text" value={query} onChange={handleChange} /><div>即时显示:{query}</div><div>{isPadding ? "延迟中..." : ""}</div></div>);
}

12、useSyncExternalStore

连接外部变量

  • const state = useSyncExternalStore(subscribe, getSnapshot)
    • state:通过getSnapshot函数返回的值
    • getSnapshost:返回外部的变量值
    • subscribe:订阅函数
      • 参数:回调函数。当state发送变化后,只有调用回调函数,才能触发组件刷新。
      • 返回值:回调函数,
        • 当组件卸载时,会调用该回调函数,用于取消订阅。
        • 当subscribe的this被修改时,每次修改数据,都会执行取消订阅
  • 注意:
    • 只能处理基础类型的数据,对象,或数组的修改,无法处理。
    • 对象或数组,可以使用JSON.stringify格式化处理
let count = 0;
const subScribers = new Set();
const countStore = {get() {return count;},sub(callback) {subScribers.add(callback);return () => {console.log("组件卸载,取消订阅");subScribers.delete(callback);};},// 数据发送变化,通知所有订阅者add() {count++;subScribers.forEach((callback) => callback());},
};function App() {let state = useSyncExternalStore(countStore.sub, countStore.get);return (<div><button onClick={countStore.add}>按钮</button><div>{state}</div></div>);
}

13、自定义

  • 定义以use前缀开头的函数
  • 函数内可以使用react自带的hook
  • 返回处理好的数据或方法
  • 如封装的useStore、useRoute、useRouteProgress、useRouteAnimate

16、组件

1、Fragment

react提供React.Fragment空文档标记,既保证只有一个根节点,又不会增加层级

const App = () => <>hello world</>

2、Suspense

占位异步加载组件

  • 判断依据Suspense组件加载的子组件,如果子组件抛出Promise.resolve或Promise.reject,都会使suspense组件判定为加载状态。

    function Box() {throw Promise.resolve();
    }function App() {return <><h1>app</h1><Suspense fallback={<p>loading...</p>}><Box /></Suspense> </>
    }
    
  • 用法一:配合lazy实现组件懒加载

    const Box = lazy(() => import("@/view/box"));function App() {return <><h1>app</h1>  <Suspense fallback={<p>loading...</p>}><Box /></Suspense>  </>;
    }
    
  • 用法二:阻塞Box渲染

    function Box() {// throw Promise.resolve(); 会阻塞渲染,显示loading// throw Promise.reject(); 会阻塞渲染,显示loadingconst data = Promise.resolve("box");console.log("padding");return <box>{data}</box>;
    }function App() {return <><h1>app</h1>  <Suspense fallback={<p>loading...</p>}><Box /></Suspense>  </>;
    }
    
  • 通过上面的案例,可以知道Box会执行两次

    • 第一次:
      • 获取到Promise异步执行
      • Suspense组件判断显示loading组件
      • 监听Promise的状态
    • 第二次:
      • 监听到Promise执行完成,获取到结果
      • 结束loading状态
      • 显示Box组件
    • 并不一定会只执行两次,而是通过对Promise的监听,判断是否数据加载完成
  • 模拟接口

    // 模拟接口1
    function api1() {return new Promise((resolve) => {setTimeout(() => {resolve("hello");}, 3000);});
    }// 模拟接口2
    function api2() {return new Promise((resolve) => {setTimeout(() => {resolve("box");}, 5000);});
    }// 接口防抖处理
    function axios(fn) {let res = null;const promise = fn;promise.then((data) => {res = data;});return function () {if (res) return res;return promise;};
    }const resPromise1 = axios(api1());
    const resPromise2 = axios(api2());function Box() {const data1 = resPromise1();const data2 = resPromise2();return (<div><p>{data1}</p><p>{data2}</p></div>);
    }function App() {return <><h1>app</h1><Suspense fallback={<p>loading...</p>}><Box /></Suspense></>;
    }
    
    • 对接口进行防抖处理
    • 此时会调用三次Box:
      • 第一次调用,会创建一个promise,此时promise还没有任何状态
      • 第二次调用,promise进行padding状态,触发第二次调用
      • 第三次调用,promise进入resolve状态,触发第三次调用,获取结果
    • 注意,如果有多个接口调用,会监听最长的响应

17、API

1、createElement

  • 已过时,执行完成后,返回虚拟dom对象
  • 引入:import { createElement } from "react";
  • const Dom = createElement(ele, props, ...children):创建虚拟dom
    • Dom:创建的Dom组件元素
    • ele:dom标签,或者react提供的组件。比如React.Fragment
    • props:属性,事件
      • className:定义类名
      • style:定义样式
      • onClick:绑定点击事件
      • data:props.data其他属性赋值,都是props
    • children:子节点的虚拟dom对象

2、Children

  • 已过时,用于处理插槽props.children
  • 引入:import { Children } from "react";
  • Children.count(props.children):获取props.children的数量
  • Children.forEach(children, (child, index) => {}):遍历
  • Children.map(children, (child, index) => ele):map
  • Children.toArray(children):返回children数组

3、forwardRef

  • 将ref注入到函数组件的第二个参数中
  • 配合useRefuseImperativeHandle完成自定义组件的读取

4、createContext

  • 创建上下文对象,并设置默认值

    const MyContext = createContext(defaultValue)

  • 上下文对象设置值

    <MyContext.Provider value={{ data }}>{children}</MyContext.Provider>

5、lazy

  • 高阶组件,实现组件懒加载
  • const AppLazy = React.lazy(App)
  • const AppLazu = React.lazy(() => import("@/view/app.jsx"))

6、memo

  • 和useMemo的情况差不多
  • 父组件内有动态时间显示,就会不停的刷新子组件。
  • 子组件如果有大量计算,就会因为刷新而不断的执行
  • 需求:子组件变成纯组件,只会由与父组件绑定的state变化影响,其他的变量不会刷新子组件
  • const PureComponent = memo(Component)
    • PureComponent:纯组件
    • Component:组件
  • 通过memo高阶组件处理,就能到的一个纯组件。输入不变,输出就不会变化
  • 避免大量的计算,提高性能

特殊情况:父组件给子组件的props包含数组时

  • 输入不变,输出就不会变
  • 如果输入的是一个数组这样的引用数据时,也就是给纯组件传递的props数据是引用类型。此时还是会被影响
  • 原因:每次刷新时,引用数据会重新生成,虽然值相同,但引用地址会发送变化,所以就导致输入其实是变化的。
  • 解决:在父组件中使用useMemo处理

7、startTransition

就是useTransition的第二个参数

和useTransition一样,把包裹的setState操作,放入非紧急处理。

18、TS开发

  • 使用create-react-app myApp --template typescript,创建使用ts开发的项目
  • npm i -s typescript @types/node @types/react @types/react-dom @types/jest
    • 添加ts到已有的项目

19、规范配置

  • create-react-app脚手架默认的eslint配置为react-appreact-app/jest

对项目eslint默认配置进行微调

  • 方式一:

    • 创建.eslintrc.js文件

      module.exports = {extends: ["react-app", "react-app/jest"],rules: {"no-console": "warn", // 如果出现打印,就报错},
      };
      
    • 然后重启项目,完成规范微调

    • 规范参考:https://eslint.nodejs.cn/docs/latest/rules/

  • 方式二:

    • package.jsoneslinConfig配置项修改
    • 添加rules配置
    • 在rules内微调

20、GIT拦截

1、格式化代码

  • 根据create-react-app脚手架官网文档

  • 下载插件:npm i -D husky lint-staged prettier

  • package.json添加配置

    {// ..."husky": {"pre-commit": "lint-staged"}
    }
    

2、commit规范

  • 使用插件@commitlint/cli能进行规范校验
  • 配置很繁琐,很少项目进行配置
  • 不建议配置commit规范,建议参考一下提交模板
[任务/bug号] 1024
[修改内容] 完成create-react-app脚手架解析

http://www.mrgr.cn/news/97166.html

相关文章:

  • vulkanscenegraph显示倾斜模型(5.6)-vsg::RenderGraph的创建
  • 【408--考研复习笔记】操作系统----知识点速览=
  • MySQL 知识点详解(索引、存储引擎、事务与隔离级别、MVCC、锁机制、优化)
  • Linux信号——信号的产生(1)
  • 浅谈AI落地 - 文章推荐 - 混合推荐模型
  • 【NLP 53、投机采样加速推理】
  • MySQL:库表操作
  • pat学习笔记
  • 【MySQL】01.MySQL环境安装
  • OpenVLA-OFT——微调VLA的三大关键设计:支持动作分块的并行解码、连续动作表示以及L1回归目标
  • 操作系统知识点(一)
  • [C++面试] new、delete相关面试点
  • 论文阅读笔记:Denoising Diffusion Implicit Models (4)
  • 从代码上深入学习GraphRag
  • YOLO 获取 COCO 指标终极指南 | 从标签转换到 COCOAPI 评估 (训练/验证) 全覆盖【B 站教程详解】
  • hi3516cv610通过menuconfig关闭的宏记录
  • 欧几里得算法求最大公约数、最小公倍数
  • UBUNTU编译datalink
  • 大模型学习四:‌DeepSeek Janus-Pro 多模态理解和生成模型 本地部署指南(折腾版)
  • 列表与列表项