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

React开发高级篇 - React Hooks以及自定义Hooks实现思路

Hooks介绍

Hooks是react16.8以后新增的钩子API;
目的:增加代码的可复用性,逻辑性,弥补无状态组件没有生命周期,没有数据管理状态state的缺陷。
为什么要使用Hooks?

  1. 开发友好,可扩展性强,抽离公共的方法或组件,Hook 使你在无需修改组件结构的情况下复用状态逻辑;
  2. 函数式编程,将组件中相互关联的部分根据业务逻辑拆分成更小的函数;
  3. class更多作为语法糖,没有稳定的提案,且在开发过程中会出现不必要的优化点,Hooks无需学习复杂的函数式或响应式编程技术;
    官网react hooks介绍:https://zh-hans.reactjs.org/docs/hooks-intro.html

React中常见Hooks介绍

useState

如何理解 useState - useState 接受两个参数:

  • 初始状态(initialState):这是 state 的初始值。
  • 更新函数(setState):这是一个你可以调用以更新 state 的函数。

useState 返回一个数组,包含两个元素:

  • 当前状态(current state):这是 state 的当前值。
  • 更新函数(setState):用于更新 state 的函数。

具体解释:
useState 允许你在函数组件内部使用状态。这是通过将状态存储在组件的执行上下文中实现的,而不是像类组件那样存储在实例对象中。每次组件重新渲染时,useState 返回的状态值都不会改变,除非你显式地调用更新函数来更新它。

const [number, setNumber] = useState(0);
import React, { useState } from 'react';function Counter() {// 声明一个名为 'count' 的 state 变量,初始值为 0const [count, setCount] = useState(0);return (<div><p>You clicked {count} times</p><button onClick={() => setCount(count + 1)}>Increase</button><button onClick={() => setCount(count - 1)}>Decrease</button></div>);
}
  1. setState支持stateless组件有自己的state;
  2. 入参:具体值或一个函数;
  3. 返回值:数组,第一项是state值,第二项负责派发数据更新,组件渲染;

注意:setState会让组件重新执行,所以一般需要配合useMemo或useCallback;

const DemoState = (props) => {/* number为此时state读取值 ,setNumber为派发更新的函数 /const [number, setNumber] = useState(0) / 0为初始值 /return (<div><span>{ number }</span><button onClick={ ()=> {setNumber(number + 1)console.log(number) / 这里的number是不能够即使改变的,返回0  /}}/></div>)
}
// 当更新函数之后,state的值是不能即时改变的,只有当下一次上下文执行的时候,state值才随之改变——————————————————————————————————————————const a =1 
const DemoState = (props) => {/  useState 第一个参数如果是函数 则处理复杂的逻辑,返回值为初始值 /let [number, setNumber] = useState(()=>{// numberreturn a === 1 ? 1 : 2}) / 1为初始值 */return (<div><span>{ number }</span><button onClick={ ()=>setNumber(number+1) } ></button></div>)
}

useEffect

  1. 使用条件:当组件init、dom render完成、操纵dom、请求数据(如componentDidMount)等;
  2. 不限制条件,组件每次更新都会触发useEffect --> componentDidUpdate 与 componentwillreceiveprops;
  3. useEffect 第一个参数为处理事件,第二个参数接收数组,为限定条件,当数组变化时触发事件,为[]只在组件初始化时触发;
  4. useEffect第一个参数有返回时,一般用来消除副作用(如去除定时器、事件绑定等);
* 模拟数据交互 /
function getUserInfo(a)return new Promise((resolve)=>{setTimeout(()=>{ resolve({name:a,age:16,}) },500)})
}const Demo = ({ a }) => {const [ userMessage , setUserMessage ] = useState({})const [number, setNumber] = useState(0)const div= useRef()const handleResize =()=>{}useEffect(()=>{getUserInfo(a).then(res=>{setUserMessage(res)})console.log(div.current) / div /window.addEventListener('resize', handleResize)/ 只有当props->a和state->number改变的时候 ,useEffect副作用函数重新执行 ,如果此时数组为空[],证明函数只有在初始化的时候执行一次相当于componentDidMount/},[ a ,number ])return (<div ref={div} ><span>{ userMessage.name }</span><span>{ userMessage.age }</span><div onClick={ ()=> setNumber(1) } >{ number }</div></div>)
}————————————————————————————————————————————————
const Demo = ({ a }) => {const handleResize =()=>{}useEffect(()=>{const timer = setInterval(()=>console.log(666),1000)window.addEventListener('resize', handleResize)/ 此函数用于清除副作用 */return function(){clearInterval(timer) window.removeEventListener('resize', handleResize)}},[ a ])return (<div></div>)
}

注意:useEffect无法直接使用async await.

// Bad
useEffect(async ()=>{/* 请求数据 */const res = await getUserInfo(payload)
},[ a ,number ])
————————————————————————————————————————————————useEffect(() => {// declare the async data fetching functionconst fetchData = async () => {const data = await fetch('https://xxx.com');const json = await data.json();return json;}// call the functionconst result = fetchData().catch(console.error);// ❌ 无效setData(result);
}, [])// 改进版
useEffect(() => {const fetchData = async () => {const data = await fetch('https://xxx.com');const json = await response.json();setData(json);}// call the functionfetchData()// make sure to catch any error.catch(console.error);;
}, [])

useLayoutEffect

渲染更新之前的 useEffect

useEffect: 组件更新挂载完成 -> 浏览器dom 绘制完成 -> 执行useEffect回调 ;
useLayoutEffect : 组件更新挂载完成 -> 执行useLayoutEffect回调-> 浏览器dom 绘制完成;

渲染组件

  1. useEffect:闪动;
  2. useLayoutEffect:卡顿;
const DemoUseLayoutEffect = () => {const target = useRef()useLayoutEffect(() => {/*我们需要在dom绘制之前,移动dom到制定位置*/const { x ,y } = getPositon() /* 获取要移动的 x,y坐标 */animate(target.current,{ x,y })}, []);return (<div ><span ref={ target } className="animate"></span></div>)
}

在 React 中,useEffect 和 useLayoutEffect 都是用于处理副作用的 Hooks,但它们的执行时机和用途有所不同。

useEffect
useEffect 用于在组件渲染后执行副作用操作,比如数据获取、订阅或手动更改 DOM。它在浏览器完成屏幕绘制后异步执行,这意味着它不会阻塞浏览器的渲染。useEffect 可以接受一个函数和一个依赖数组作为参数。如果提供了依赖数组,当数组中的值发生变化时,useEffect 内的函数会重新执行。

import React, { useState, useEffect } from 'react';function FriendStatus({ friend }) {const [isOnline, setIsOnline] = useState(null);useEffect(() => {function handleStatusChange(status) {setIsOnline(status.isOnline);}ChatAPI.subscribeToFriendStatus(friend.id, handleStatusChange);// 组件卸载时,取消订阅return () => {ChatAPI.unsubscribeFromFriendStatus(friend.id, handleStatusChange);};}, [friend.id]); // 依赖数组,只有当 friend.id 变化时才重新执行if (isOnline === null) {return 'Loading...';}return isOnline ? 'Online' : 'Offline';
}

在这个例子中,useEffect 用于订阅好友状态变化,并在组件卸载时取消订阅,防止内存泄漏

useLayoutEffect
useLayoutEffect 与 useEffect 类似,但它在 DOM 更新之前同步执行,这意味着它可能会阻塞浏览器的渲染,因此使用时需要谨慎。useLayoutEffect 适合那些需要在 DOM 更新之前同步读取 DOM 信息的场景。

import React, { useState, useLayoutEffect } from 'react';function Tooltip({ children, targetRect }) {const [tooltipHeight, setTooltipHeight] = useState(0);useLayoutEffect(() => {const { height } = children.getBoundingClientRect();setTooltipHeight(height);}, []);let tooltipX = 0;let tooltipY = 0;if (targetRect !== null) {tooltipX = targetRect.left;tooltipY = targetRect.top - tooltipHeight;if (tooltipY < 0) {tooltipY = targetRect.bottom;}}return (<div style={{ position: 'absolute', left: tooltipX, top: tooltipY }}>{children}</div>);
}

在这个例子中,useLayoutEffect 用于测量 tooltip 的高度,并在 DOM 更新之前同步设置状态,以确保 tooltip 位置正确.
总结来说,useEffect 适合大多数副作用场景,特别是那些不需要同步执行的操作。而 useLayoutEffect 则用于需要在 DOM 更新之前同步执行的特定场景,但应谨慎使用,因为它可能会影响性能。

useRef

用来获取元素、缓存数据;
入参可以作为初始值.

// 获取元素
const DemoUseRef = ()=>{const dom= useRef(null)const handerSubmit = ()=>{/*  <div >表单组件</div>  dom 节点 */console.log(dom.current)}return <div><div ref={dom} >表单组件</div><button onClick={()=>handerSubmit()} >提交</button> </div>
}// 缓存数据,小技巧
// 不同于useState,useRef改变值不会使comp re-render
const currenRef = useRef(InitialData)
currenRef.current = newValue

useContext

用来获取父级组件传递过来的context值,这个当前值就是最近的父级组件 Provider 的value;

从parent comp获取ctx方式;

  1. useContext(Context);
  2. Context.Consumer;
/* 用useContext方式 /
const DemoContext = ()=> {const value = useContext(Context);/ my name is aaa /return <div> my name is { value.name }</div>
}/ 用Context.Consumer 方式 /
const DemoContext1 = ()=>{return <Context.Consumer>{/  my name is aaa  */}{ (value)=> <div> my name is { value.name }</div> }</Context.Consumer>
}export default ()=>{return <div><Context.Provider value={{ name:'aaa' }} ><DemoContext /><DemoContext1 /></Context.Provider></div>
}

useReducer

入参:

  1. 第一个为函数,可以视为reducer,包括state 和 action,返回值为根据action的不同而改变后的state;
  2. 第二个为state的初始值;

出参:

  1. 第一个更新后的state值;
  2. 第二个是派发更新的dispatch函数;执行dispatch会导致组件re-render;(另一个是useState)
const DemoUseReducer = ()=>{/* number为更新后的state值,  dispatchNumbner 为当前的派发函数 /const [ number , dispatchNumbner ] = useReducer((state, action) => {const { payload , name  } = action/ return的值为新的state /switch(name) {case 'a':return state + 1case 'b':return state - 1 case 'c':return payload       }return state}, 0)return <div>当前值:{ number }{ / 派发更新 / }<button onClick={()=>dispatchNumbner({ name: 'a' })} >增加</button><button onClick={()=>dispatchNumbner({ name: 'b' })} >减少</button><button onClick={()=>dispatchNumbner({ name: 'c' , payload:666 })} >赋值</button>{ / 把dispatch 和 state 传递给子组件  */ }<MyChildren  dispatch={ dispatchNumbner } State={{ number }} /></div>
}

业务中经常将 useReducer+useContext 代替Redux.

useMemo

用来根据useMemo的第二个参数deps(数组)判定是否满足当前的限定条件来决定是否执行第一个cb;

// selectList 不更新时,不会重新渲染,减少不必要的循环渲染
useMemo(() => (<div>{selectList.map((i, v) => (<spanclassName={style.listSpan}key={v} >{i.patentName} </span>))}</div>
), [selectList])————————————————————————————————————————————————————
// listshow, cacheSelectList 不更新时,不会重新渲染子组件
useMemo(() => (<Modalwidth={'70%'}visible={listshow}footer={[<Button key="back" >取消</Button>,<Buttonkey="submit"type="primary">确定</Button>]}> { /* 减少了PatentTable组件的渲染 / }<PatentTablegetList={getList}selectList={selectList}cacheSelectList={cacheSelectList}setCacheSelectList={setCacheSelectList}/></Modal>), [listshow, cacheSelectList])————————————————————————————————————————————————————// 减少组件更新导致函数重新声明const DemoUseMemo = () => {/ 用useMemo 包裹之后的log函数可以避免了每次组件更新再重新声明 ,可以限制上下文的执行 /const newLog = useMemo(() => {const log = () => {console.log(123)}return log}, [])return <div onClick={()=> newLog() } ></div>
}————————————————————————————————————————————————————
// 如果没有加相关的更新条件,是获取不到更新之后的state的值的
const DemoUseMemo = () => {const [ number ,setNumber ] = useState(0)const newLog = useMemo(() => {const log = () => {/ 点击span之后 打印出来的number 不是实时更新的number值 /console.log(number)}return log/ [] 没有 number */  }, [])return <div><div onClick={() => newLog()} >打印</div><span onClick={ () => setNumber( number + 1 )  } >增加</span></div>
}

useCallback

useMemo返回cb的运行结果;
useCallback返回cb的函数;

import React, { useState, useCallback } from 'react'function Button(props) {const { handleClick, children } = props;console.log('Button -> render');return (<button onClick={handleClick}>{children}</button>)
}const MemoizedButton = React.memo(Button);export default function Index() {const [clickCount, increaseCount] = useState(0);const handleClick = () => {console.log('handleClick');increaseCount(clickCount + 1);}return (<div><p>{clickCount}</p><MemoizedButton handleClick={handleClick}>Click</MemoizedButton></div>)
}// MemoizedButton还是重新渲染了
// Index组件state发生变化,导致组件重新渲染;
// 每次渲染导致重新创建内部函数handleClick ,
// 进而导致子组件Button也重新渲染。import React, { useState, useCallback } from 'react'function Button(props) {const { handleClick, children } = props;console.log('Button -> render');return (<button onClick={handleClick}>{children}</button>)
}const MemoizedButton = React.memo(Button);export default function Index() {const [clickCount, increaseCount] = useState(0);// 这里使用了`useCallback`const handleClick = useCallback(() => {console.log('handleClick');increaseCount(clickCount + 1);}, [])return (<div><p>{clickCount}</p><MemoizedButton handleClick={handleClick}>Click</MemoizedButton></div>)
}

Hooks 实际应用

所有依赖都必须放在依赖数组中么?

useEffect 中,默认有个共识: useEffect 中使用到外部变量,都应该放到第二个数组参数中。

// 当props.count 和 count 变化时,上报数据
function Demo(props) {const [count, setCount] = useState(0);const [text, setText] = useState('');const [a, setA] = useState('');useEffect(() => {monitor(props.count, count, text, a);}, [props.count, count]);return (<div><buttononClick={() => setCount(count => count + 1)}>click</button><input value={text} onChange={e => setText(e.target.value)} /><input value={a} onChange={e => setA(e.target.value)} /></div>)
}

此时,text 和 a 变量没有放在dps 数组中。

在这里插入图片描述
如果把text 和 a 也引入deps中,当text 和 a改变时,也触发了函数执行
Solution:

  1. 不要使用 eslint-plugin-react-hooks 插件,或者可以选择性忽略该插件的警告;
  2. 只有一种情况,需要把变量放到 deps 数组中,那就是当该变量变化时,需要触发 useEffect 函数执行。而不是因为 useEffect 中用到了这个变量!

尽量不要用useCallback

  1. useCallback 大部分场景没有提升性能
  2. useCallback让代码可读性变差
Example 1
const someFunc = useCallback(()=> {doSomething();
}, []);
return <ExpensiveComponent func={someFunc} />const ExpensiveComponent = ({ func }) => {return (<div onClick={func}>hello</div>)
}// 必须用React.memo wrapper 住子组件,才能避免在参数不变的情况下,不重复渲染
// 所以一般项目中不建议使用useCallback
const ExpensiveComponent = React.memo(({ func }) => {return (<div onClick={func}>hello</div>)
}// Example 2
const someFuncA = useCallback((d, g, x, y)=> {doSomething(a, b, c, d, g, x, y);
}, [a, b, c]);const someFuncB = useCallback(()=> {someFuncA(d, g, x, y);
}, [someFuncA, d, g, x, y]);useEffect(()=>{someFuncB();
}, [someFuncB]);// 依赖层层传递,最终要找到哪些出发了useEffect执行,所以直接引用就好
const someFuncA = (d, g, x, y)=> {doSomething(a, b, c, d, g, x, y);
};const someFuncB = ()=> {someFuncA(d, g, x, y);
};useEffect(()=>{someFuncB();
}, [...]);

useMemo建议适当使用

在deps不变,且非简单的基础类型运算的情况下建议使用.

// 没有使用 useMemo
const memoizedValue = computeExpensiveValue(a, b);
// 使用 useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);// 如果没有使用 useMemo,computeExpensiveValue 会在每一次渲染的时候执行;
// 如果使用了 useMemo,只有在 a 和 b 变化时,才会执行一次 computeExpensiveValue。const a = 1;
const b = 2;
const c = useMemo(()=> a + b, [a, b]);
const c = a + b; // 内存消耗少

useState的正确使用姿势

  1. 能用其他状态计算出来就不用单独声明状态。一个 state 必须不能通过其它 state/props 直接计算出来,否则就不用定义 state
  2. 保证数据源唯一,在项目中同一个数据,保证只存储在一个地方
  3. useState 适当合并
// Example 1
const SomeComponent = (props) => {const [source, setSource] = useState([{type: 'done', value: 1},{type: 'doing', value: 2},])const [doneSource, setDoneSource] = useState([])const [doingSource, setDoingSource] = useState([])useEffect(() => {setDoingSource(source.filter(item => item.type === 'doing'))setDoneSource(source.filter(item => item.type === 'done'))}, [source])return (<div>.....</div>)
}const SomeComponent = (props) => {const [source, setSource] = useState([{type: 'done', value: 1},{type: 'doing', value: 2},])const doneSource = useMemo(()=> source.filter(item => item.type === 'done'), [source]);const doingSource = useMemo(()=> source.filter(item => item.type === 'doing'), [source]);return (<div>.....</div>)
}// 避免props层层传递,在CR中很难看清楚// Example 2
function SearchBox({ data }) {const [searchKey, setSearchKey] = useState(getQuery('key'));const handleSearchChange = e => {const key = e.target.value;setSearchKey(key);history.push(`/movie-list?key=${key}`);}return (<inputvalue={searchKey}placeholder="Search..."onChange={handleSearchChange}/>);
}function SearchBox({ data }) {const searchKey = parse(localtion.search)?.key;const handleSearchChange = e => {const key = e.target.value;history.push(`/movie-list?key=${key}`);}return (<inputvalue={searchKey}placeholder="Search..."onChange={handleSearchChange}/>);
}// url params 和 state重复了// Example 3
const [firstName, setFirstName] = useState();
const [lastName, setLastName] = useState();
const [school, setSchool] = useState();
const [age, setAge] = useState();
const [address, setAddress] = useState();
const [weather, setWeather] = useState();
const [room, setRoom] = useState();const [userInfo, setUserInfo] = useState({firstName,lastName,school,age,address
});
const [weather, setWeather] = useState();
const [room, setRoom] = useState();// 更新一个时
setUserInfo(s=> ({...s,fristName,
}))

自定义Hooks

注意:自定义Hooks本质上还是实现一个函数,关键在于实现逻辑
一般实现效果如:

const [ a[, b, c...] ] = useXXX(arg1[, arg2, ...])
import { useEffect } from 'react'const useTitle = (title) => {useEffect(() => {document.title = title}, [])return
}export default useTitleconst App = () => {useTitle('new title')return <div>home</div>
}

Update Hooks

import { useState } from 'react'const useUpdate = () => {const [, setFlag] = useState()const update = () => {setFlag(Date.now())}return update
}export default useUpdate// 实际使用
const App = (props) => {// ...const update = useUpdate()return <div>{Date.now()}<div><button onClick={update}>update</button></div></div>
}

useScroll Hooks

import { useState, useEffect } from 'react'const useScroll = (scrollRef) => {const [pos, setPos] = useState([0,0])useEffect(() => {function handleScroll(e){setPos([scrollRef.current.scrollLeft, scrollRef.current.scrollTop])}scrollRef.current.addEventListener('scroll', handleScroll)return () => {scrollRef.current.removeEventListener('scroll', handleScroll)}}, [])return pos
}export default useScroll// 用法
import React, { useRef } from 'react'
import { useScroll } from 'hooks'const Home = (props) => {const scrollRef = useRef(null)const [x, y] = useScroll(scrollRef)return <div><div ref={scrollRef}><div className="innerBox"></div></div><div>{ x }, { y }</div></div>
}

Hooks VS HOC

  1. Hook最典型的就是取代掉生命周期中大多数的功能,可以把更相关的逻辑放在一起,而非零散在各个生命周期方法中;
  2. 高阶组件可以将外部的属性功能到一个基础 Component 中,更多作为扩展能力的插件(如 react-swipeable-views中的 autoPlay 高阶组件,通过注入状态化的 props 的方式对组件进行功能扩展,而不是直接将代码写在主库中);
  3. Hook 的写法可以让代码更加紧凑,更适合做 Controller 或者需要内聚的相关逻辑,一般与目标组件内强依赖,HOC更强调对原先组件能力的扩展;
  4. 目前 Hook 还处于相对早期阶段(React 16.8.0 才正式发布Hook 稳定版本),一些第三方的库可能还暂时无法兼容 Hook;

异步组件

随着项目的增长,代码包也会随之增长,尤其是在引入第三方的库的情况下,要避免因体积过大导致加载时间过长。
React16.6中,引入了 React.lazy 和 React.Suspense 两个API,再配合动态 import() 语法就可以实现组件代码打包分割和异步加载。
传统模式:渲染组件-> 请求数据 -> 再渲染组件
异步模式:请求数据-> 渲染组件

// demo
import React, { lazy, Suspense } from 'react';
// lazy 和 Suspense 配套使用,react原生支持代码分割
const About = lazy(() => import(/* webpackChunkName: "about" */'./About'));
class App extends React.Component {render() {return (<div className="App"><h1>App</h1><Suspense fallback={<div>loading</div>}><About /></Suspense></div>);}
}
export default App;

前置基础

  1. 动态import
    相对于静态import的 import XX from XXX,动态import指在运行时加载
import('./test.js').then(test => {// ...
});
// 可见,是实现了Promsie规范的,回调函数为返回的模块
  1. 错误边界
    React V 16中引入,部分UI的JS错误不会导致整个应用崩溃;
    错误边界是一种 React 组件,错误边界在 渲染期间、生命周期方法和整个组件树的构造函数 中捕获错误,且会渲染出备用UI而不是崩溃的组件。
// comp ErrorBoundary 
import React from 'react'class ErrorBoundary extends React.Component {constructor(props) {super(props);this.state = { hasError: false };}static getDerivedStateFromError(error) {// 更新 state 使下一次渲染能够显示降级后的 UIreturn { hasError: true };}componentDidCatch(error, errorInfo) {// 你同样可以将错误日志上报给服务器console.log(error, errorInfo)}render() {if (this.state.hasError) {// 你可以自定义降级后的 UI 并渲染return <h1>Something went wrong.</h1>;}return this.props.children;}
}
export default ErrorBoundary// comp App
import React, from 'react';
import ErrorBoundary from './ErrorBoundary'
class App extends React.Component {state = {count: 1}render() {const { count } = this.stateif (count === 3) {throw new Error('I crashed!');}return (<ErrorBoundary><h1>App</h1><p>{count}</p><button onClick={() => this.setState({ count: count + 1 })}>add</button></ErrorBoundary>)}
}
export default App;

手写异步组件

Suspense组件需要等待异步组件加载完成再渲染异步组件的内容。

  1. lazy wrapper住异步组件,React第一次加载组件的时候,异步组件会发起请求,并且抛出异常,终止渲染;
  2. Suspense里有componentDidCatch生命周期函数,异步组件抛出异常会触发这个函数,然后改变状态使其渲染fallback参数传入的组件;
  3. 异步组件的请求成功返回之后,Suspense组件再次改变状态使其渲染正常子组件(即异步组件);
// comp About
const About = lazy(() => new Promise(resolve => {setTimeout(() => {resolve({default: <div>component content</div>})}, 1000)
}))// comp Suspense
import React from 'react'
class Suspense extends React.PureComponent {/*** isRender 异步组件是否就绪,可以渲染/state = {isRender: true}componentDidCatch(e) {this.setState({ isRender: false })e.promise.then(() => {/ 数据请求后,渲染真实组件 */this.setState({ isRender: true })})}render() {const { fallback, children } = this.propsconst { isRender } = this.statereturn isRender ? children : fallback}
}export default Suspense// comp lazy
import React, { useEffect } from 'react'
export function lazy(fn) {const fetcher = {status: 'pending',result: null,promise: null,}return function MyComponent() {const getDataPromise = fn()fetcher.promise = getDataPromisegetDataPromise.then(res => {fetcher.status = 'resolved'fetcher.result = res.default})useEffect(() => {if (fetcher.status === 'pending') {throw fetcher}}, [])if (fetcher.status === 'resolved') {return fetcher.result}return null}
}// 实现的效果与React支持内容保持一致
import React, {Suspese, lazy} from 'react'const About= lazy(() => { import('../About') });class App extends React.Component {render() {/*** 1. 使用 React.Lazy 和 import() 来引入组件* 2. 使用<React.Suspense></React.Suspense>来做异步组件的父组件,并使用 fallback 来实现组件未加载完成时展示信息* 3. fallback 可以传入html,也可以自行封装一个统一的提示组件*/return (<div><Suspensefallback={<Loading />}><About /></Suspense></div>)}
}
export default ReactComp;

相关文章:

React开发高级篇 - React Hooks以及自定义Hooks实现思路

Hooks介绍 Hooks是react16.8以后新增的钩子API&#xff1b; 目的&#xff1a;增加代码的可复用性&#xff0c;逻辑性&#xff0c;弥补无状态组件没有生命周期&#xff0c;没有数据管理状态state的缺陷。 为什么要使用Hooks&#xff1f; 开发友好&#xff0c;可扩展性强&#…...

shell条件测试

一.命令执行结果判定 && 在命令执行后如果没有任何报错时会执行符号后面的动作 || 在命令执行后如果命令有报错会执行符号后的动作 示例&#xff1a; [rootqingdeng shell3]# sh sl.sh /mnt/file is not exist no二.条件判断方法 在 shell 程序中&#xff0c;用户可…...

嵌入式蓝桥杯学习5 定时中断实现按键

Cubemx配置 打开cubemx。 前面的配置与前文一样&#xff0c;这里主要配置基本定时器的定时功能。 1.在Timer中点击TIM6&#xff0c;勾选activated。配置Parameter Settings中的预分频器&#xff08;PSC&#xff09;和计数器&#xff08;auto-reload Register&#xff09; 补…...

Linux下进程地址空间

文章目录 1. 进程地址空间分布2. 为什么要有进程地址空间一、主要功能二、重要特性三、应用场景四、与TLB的交互 3. 进程具有独立性 以x86(32位)为例子 1. 进程地址空间分布 进程地址空间&#xff0c;本质是一个描述进程可视范围的大小。 地址空间本质是一个内核数据结构,类似…...

基于SpringBoot的养老院管理系统的设计与实现

一、前言 随着人口老龄化的加剧&#xff0c;养老院作为老年人养老的重要场所&#xff0c;其管理的高效性和科学性显得尤为重要。传统的养老院管理方式多依赖人工操作&#xff0c;存在信息记录不及时、不准确&#xff0c;管理流程繁琐&#xff0c;资源调配困难等问题。利用信息技…...

GA-Kmeans-Transformer-GRU时序聚类+状态识别组合模型,创新发文无忧!

GA-Kmeans-Transformer-GRU时序聚类状态识别组合模型&#xff0c;创新发文无忧&#xff01; 目录 GA-Kmeans-Transformer-GRU时序聚类状态识别组合模型&#xff0c;创新发文无忧&#xff01;效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.GA-Kmeans-Transformer-GRU时…...

Hadoop单机搭建手册

hadoop搭建安装指导手册&#xff0c;包含hadoop-3.1.1、hive-3.1.0、zookeeper-3.4.6、hbase-2.3.0、spark-3.3.0等组件版本。文档详细21页&#xff0c;附带所有软件包。笔者发现很多人对于如何快速高效的单机搭建不太清楚&#xff0c;所以笔者整理了这个文档&#xff0c;希望可…...

【射频IC进阶实践教程】2.6 LNA版图设计及DRC/LVS验证

射频集成电路的版图设计非常关键&#xff0c;他对寄生参数非常敏感&#xff0c;需要使其最小化。还需要注意相互耦合的方式本次课程主要介绍射频IC的一些相关布局和连线方面的考虑。 一、版图设计 1. 版图的元件布局 首先打开对应的原理图 点击进行版图设计 由于已经有做好的…...

mac下载安装jdk

背景 长时间不折腾mac全部忘记 特此记录 安装 1.下载jdk 根据需要下载对应的jdk 我直接 下载到/Applicatiions目录 https://www.oracle.com/java/technologies/downloads/#java8-mac 2.解压 cd /Applicatiions tar -zxvf jdk-8u431-macosx-x64.tar.gz 3.配置环境 …...

【uniapp】swiper切换时,v-for重新渲染页面导致文字在视觉上的拉扯问题

问题描述 先用v-for渲染了几个列表&#xff0c;但这几个列表是占同一个位置的&#xff0c;只是通过切换swiper来显示哪个列表显示&#xff0c;也就是为了优化页面切换时候&#xff0c;没有根据swiper的current再更新v-for的数据&#xff0c;但现在就有个问题&#xff0c;怎么隐…...

shell自动显示当前git的branch

效果简介&#xff1a; 1. 如果没在git仓库&#xff0c;显示无变化 2. 如果在git仓库&#xff0c;显示当前分支 实现方法&#xff1a; 在~/.bashrc 里添加&#xff1a; function git_branch { test -d .git && branch"git branch | grep "^\*" | sed…...

使用 Acme.sh 自动生成和续签免费 SSL 证书(含通配符支持)

Acme.sh 是一个开源的脚本&#xff0c;能够从 ZeroSSL、Let’s Encrypt 等证书颁发机构&#xff08;CA&#xff09;获取免费的 HTTPS 证书。该脚本特别简单易用&#xff0c;并且支持多种验证方式。下面将详细介绍使用 Acme.sh 生成、安装和更新证书的各个步骤。 Github地址 使用…...

【JAVA】Java高级:Spring框架与Java EE—Web开发基础(Servlet、JSP)

Java作为一种广泛使用的编程语言&#xff0c;提供了强大的Web开发框架和技术&#xff0c;其中Servlet和JSP&#xff08;JavaServer Pages&#xff09;是构建动态Web应用的基础。了解这些技术对于任何想要深入Java Web开发的程序员来说都是必不可少的。 一、Web开发的重要性 动…...

pytorch生成对抗网络

# 生成对抗网络 import os import torch import torchvision import torch.nn as nn from torchvision import transforms from torchvision.utils import save_image # Device configuration device torch.device(cuda if torch.cuda.is_available() else cpu) # 超参数 late…...

flask简易版的后端服务创建接口(python)

1.pip install安装Flask和CORS 2.创建http_server.py文件,内容如下 """ ============================ 简易版的后端服务 ============================ """ from flask import Flask, request, jsonify from flask_cors import CORS app = F…...

gitlab 生成并设置 ssh key

一、介绍 &#x1f3af; 本文主要介绍 SSH Key 的生成方法&#xff0c;以及如何在GitLab上添加SSH Key。GitLab 使用SSH协议与Git 进行安全通信。当您使用 SSH密钥 对 GitLab远程服务器进行身份验证时&#xff0c;您不需要每次都提供您的用户名和密码。SSH使用两个密钥&#x…...

ssh远程升级Ubuntu20.04到Ubuntu 22.04

ssh远程升级Ubuntu20.04到Ubuntu 22.04 陈拓 2024/10/16-2024/10/26 1. 简介 本文介绍了如何通过ssh将Ubuntu系统从20.04升级到22.04。 在进行系统升级之前&#xff0c;建议备份重要数据&#xff0c;以防升级过程中出现问题。 2. 更新当前系统 硬件系统架构 当前操作系统版…...

Qt开源控件:图像查看器工具V1.1

一、项目概述 本项目是一款基于 Qt 框架 开发的 图像查看工具&#xff0c;可以显示带坐标轴的图像&#xff0c;并实时获取图像中任意像素点的坐标和颜色信息。工具具有图像缩放、动态坐标轴绘制、鼠标交互等功能&#xff0c;使用起来方便直观。 二、功能亮点 1. 图像加载与显…...

【WRF-Urban】SLUCM新增空间分布城市冠层参数及人为热排放AHF代码详解(下)

目录 详细解释更改文件内容4 运行模块(run):README.namelist5 输出模块(share):share/module_check_a_mundo.Fshare/output_wrf.F参考SLUCM新增空间分布城市冠层参数及人为热排放AHF代码详解的前两部分内容可参见-【WRF-Urban】SLUCM新增空间分布城市冠层参数及人为热排放A…...

【C#】新建窗体文件,Form、UserControl

从用途、功能性和架构方面进行描述。 1. 继承自 Form 的窗体&#xff08;通常是窗口&#xff09;&#xff1a; 在 C# 中&#xff0c;Form 是用于创建应用程序的主窗口或对话框窗口的类。当您继承自 Form 时&#xff0c;您创建的是一个完整的窗口&#xff0c;可以显示内容、与…...

优化SEO策略掌握长尾关键词的力量

内容概要 在数字营销领域&#xff0c;SEO&#xff08;搜索引擎优化&#xff09;是帮助网站获得更多流量的关键。然而&#xff0c;随着在线竞争的加剧&#xff0c;单纯依赖短尾关键词已难以满足用户的搜索需求。这时&#xff0c;长尾关键词的引入便显得尤为重要。长尾关键词通常…...

MySQL分页查询

分页查询&#xff1a; 数据记录条数过多的时候&#xff0c;需要分页来显示。 语法&#xff1a; select 查询字段 from 表名 where ....等等前面学过的所有写法 limit offset&#xff08;开始记录索引,是从0开始的&#xff09;,size&#xff08;要取出的条数&#xff09;&…...

执行“go mod tidy”遇到“misbehavior”错误

执行“go mod tidy”报错下错误&#xff0c;执行“go clean -modcache”和删除“go env GOMODCACHE”指定目录均无效&#xff1a; SECURITY ERROR go.sum database server misbehavior detected!old database:go.sum database tree3397826xyyhzdyAOat5li/EXx/MK1gONQf3LAGqArh…...

【机器学习】——windows下安装anaconda并在vscode上进行配置

一、安装anaconda 1.进入清华的镜像网站&#xff0c;下载自己电脑对应的anaconda版本。网站&#xff1a;https://repo.anaconda.com/archive/ 这里我下载的版本是anaconda3-2024.10-1-Windows-x86-64 2.下载完毕后开始安装anaconda 3.配置anaconda环境变量 在设置中找到编…...

第6章:布局 --[CSS零基础入门]

CSS 布局是网页设计中至关重要的一个方面&#xff0c;它决定了页面上元素的排列和展示方式。以下是几种常见的 CSS 布局方法和技术&#xff1a; 1. 浮动布局&#xff08;Float Layout&#xff09; 浮动布局&#xff08;Float Layout&#xff09;曾经是网页设计中创建多列布局…...

kubeadm安装K8s集群基础环境配置

kubeadm安装K8s集群基础环境配置 1.首先确保所有机器可以通信&#xff0c;然后配置主机hosts文件&#xff1b;2.关闭所有节点关闭防火墙、selinux、swap&#xff1b;3.将桥接的IPv4流量传递到 iptables&#xff1b;4.安装常用工具包&#xff1b;5.安装时间同步工具ntpdate&…...

计算机毕业设计Python医疗问答系统 医疗可视化 BERT+LSTM+CRF深度学习识别模型 机器学习 深度学习 爬虫 知识图谱 人工智能 大数据毕业设计

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…...

学在西电录播课使用python下载,通过解析m3u8协议、多线程下载ts视频块以及ffmpeg合并

本文涵盖的内容仅供个人学习使用&#xff0c;如果侵犯学校权利&#xff0c;麻烦联系我删除。 初衷 研究生必修选逃&#xff0c; 期末复习怕漏过重点题目&#xff0c;但是看学在西电的录播回放课一卡一卡的&#xff0c;于是想在空余时间一个个下载下来&#xff0c;然后到时候就…...

攻防世界杂项刷题笔记(引导模式)13-23

引言&#xff1a;14包括提取文件和流量分析&#xff0c;22很新颖&#xff01;&#xff01;其他的都是常规隐写 13.base64stego 经过上一次如来十三掌的磨练&#xff0c;这题在看到题干“十三掌”的时候我是丝毫不慌张的。附件给了压缩包&#xff0c;考虑是不是伪加密&#xf…...

基于单片机的智能农田灌溉节水系统设计及应用

摘 要 &#xff1a; 针对传统的灌溉方法浪费水资源节水系统设计。该系统从节水角度出发&#xff0c;对传感器和主电路进行了设计&#xff0c;主要采集灌溉地的湿度与温度数据&#xff0c;根据测量土壤中的温度与湿度作为主要参数&#xff0c;对农田灌溉节水系统进行实时控制&am…...

高校毕业生离校就业数据分析管理平台的设计与实现(Java毕业设计)教务管理、就业统计

系统合集跳转 源码获取链接 一、系统环境 运行环境: 最好是java jdk 1.8&#xff0c;我们在这个平台上运行的。其他版本理论上也可以。 IDE环境&#xff1a; Eclipse,Myeclipse,IDEA或者Spring Tool Suite都可以 tomcat环境&#xff1a; Tomcat 7.x,8.x,9.x版本均可 操作系统…...

1、SQL语言

分类方式 类别描述 部署方式 嵌入式/单机/双机/集群/分布式/云数据库 业务类型 OLTP数据库/OLAP数据库/流数据库/时序数据库 存储介质 内存数据库/磁盘数据库/SSD数据库/SCM数据库 年代 第一代是单机数据库/第二代是集群数据库/第三代是分布式数据库和云原生数据库/第…...

spark sql 环境安装,java 默认路径和 安装配置!

yum安装java 查看默认路径 update-alternatives --config java # Java 环境变量 export JAVA_HOME/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.412.b08-1.el7_9.x86_64/jreexport PATH$JAVA_HOME/bin:$PATH# Spark 环境变量 export SPARK_HOME/home/vagrant/soft/sparkexport PATH…...

【CMD、PowerShell和Bash设置代理】

【CMD、PowerShell和Bash设置代理】 1. CMD&#xff08;命令提示符&#xff09;临时设置代理&#xff08;只对当前会话有效&#xff09;&#xff1a;查看当前代理设置&#xff1a;清除临时代理设置&#xff1a;永久设置代理&#xff08;对所有新的 CMD 会话有效&#xff09;&am…...

分区之间的一种度量方法-覆盖度量(Covering Metric)

分区之间的一种度量方法——覆盖度量&#xff08;Covering Metric&#xff09;&#xff0c;用于量化一个分区如何被另一个分区覆盖或近似。以下是逐步详细解释&#xff1a; 1. 背景与符号说明 分区的概念&#xff1a; 分区是将一个集合&#xff08;这里是 { 1 , … , n } \{…...

HarmonyOS(64) wrapBuilder 全局@Builder使用利器

WrapBuilder 全局Builder是什么什么时候使用wrapBuilderBuilder的限制参考资料 全局Builder是什么 局部Builder的定义方法如下&#xff1a; //定义局部Builder Builder MyBuilderFunction() {} //使用方法 this.MyBuilderFunction()全局Builder定义语法如下&#xff1a; //全…...

【计算机毕设】基于Spark猫眼电影票房数据分析预测推荐系统(完整系统源码+数据库+开发笔记+详细部署教程+虚拟机分布式启动教程)✅

目录 【计算机毕设】基于Spark猫眼电影票房数据分析预测推荐系统&#xff08;完整系统源码数据库开发笔记详细部署教程虚拟机分布式启动教程&#xff09;✅ 一、项目背景 二、研究目的 三、项目意义 四、项目功能 五、项目创新点 六、开发技术介绍 七、算法介绍 八、数…...

问卷调查模板

<!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>问卷调查</title><style>body {font-fam…...

hadoop单机安装

步骤 1:安装 Java 安装 OpenJDK bash sudo yum install -y java-1.8.0-openjdk 验证 Java 安装 bash java -version 输出类似以下内容表示成功: arduino openjdk version “1.8.0_xxx” 步骤 2:下载 Hadoop 下载 Hadoop 安装包 前往 Hadoop 官方下载页面,获取最新稳…...

Android笔记【15】跳转页面返回信息

一、问题 学习一段代码 val intent Intent(thisSecondActivity, MainActivity::class.java) intent.putExtra("extra_data", data) startActivity(intent) 二、内容 这段代码是在 Android 应用中启动一个新的活动&#xff08;Activity&#xff09;&#xff0c;具…...

MATLAB深度学习(七)——ResNet残差网络

一、ResNet网络 ResNet是深度残差网络的简称。其核心思想就是在&#xff0c;每两个网络层之间加入一个残差连接&#xff0c;缓解深层网络中的梯度消失问题 二、残差结构 在多层神经网络模型里&#xff0c;设想一个包含诺干层自网络&#xff0c;子网络的函数用H(x)来表示&#x…...

推荐几种主流数据仓库:深度剖析与对比

引言 随着数据量的不断增长&#xff0c;数据仓库技术在企业的数据管理和分析中扮演着越来越重要的角色。不同的数据仓库系统在性能、架构、功能和适用场景上各有特点。本文将详细介绍目前市场上几种主流的数据仓库&#xff0c;帮助你更好地了解各个数据仓库的特性&#xff0c;选…...

编译原理——词法分析器的实现

实验目的 深入理解有限自动机及其应用编辑一个词法分析器&#xff0c;了解计算机识别源程序字符串的过程。 实验内容和要求 实验内容&#xff1a;处理c语言源程序&#xff0c;对源程序进行编译预处理&#xff08;去除注释、无用的回车换行找到包含的文件等&#xff09;之后&a…...

【MySQL】数据库 Navicat 可视化工具与 MySQL 命令行基本操作

&#x1f4af; 欢迎光临清流君的博客小天地&#xff0c;这里是我分享技术与心得的温馨角落 &#x1f4af; &#x1f525; 个人主页:【清流君】&#x1f525; &#x1f4da; 系列专栏: 运动控制 | 决策规划 | 机器人数值优化 &#x1f4da; &#x1f31f;始终保持好奇心&…...

hive分区分桶、数据倾斜总结

一、hive的基本概念 hive是一个构建在hadoop上的数据仓库工具&#xff0c;可以将结构化的数据文件映射为一张数据库表并提供数据查询功能 二、hive的特点 &#xff08;1&#xff09;数据是存储在hdfs上 &#xff08;2&#xff09;底层是将sql转换为MapReduce任务进行计算 …...

MySQL 函数

在 MySQL 中&#xff0c;函数&#xff08;Function&#xff09;是一种用于封装一段逻辑处理的编程结构&#xff0c;可以在 SQL 语句中调用并返回单个值。函数和存储过程类似&#xff0c;都是存储在服务器端的程序单元&#xff0c;但它们的应用场景和使用方式有所不同。函数通常…...

Java 并发舞台:多线程小精灵的奇幻冒险之旅

1.线程池的拒绝策略有哪些&#xff1f; Java中的线程池提供了几种不同的拒绝策略&#xff0c;当线程池无法处理新的任务时&#xff08;比如因为线程池已满并且工作队列也满了&#xff09;&#xff0c;这些策略会决定如何处理新提交的任务。ThreadPoolExecutor类中定义了以下四…...

PostgreSQL 性能优化全方位指南:深度提升数据库效率

PostgreSQL 性能优化全方位指南&#xff1a;深度提升数据库效率 别忘了请点个赞收藏关注支持一下博主喵&#xff01;&#xff01;&#xff01; 在现代互联网应用中&#xff0c;数据库性能优化是系统优化中至关重要的一环&#xff0c;尤其对于数据密集型和高并发的应用而言&am…...

【Go 基础】并发相关

并发相关 CAS CAS算法&#xff08;Compare And Swap&#xff09;&#xff0c;是原⼦操作的⼀种,&#xff0c;CAS 算法是⼀种有名的⽆锁算法。⽆锁编程&#xff0c;即不使⽤锁的情况下实现多线程之间的变量同步。可⽤于在多线程编程中实现不被打断的数据交换操作&#xff0c;从…...

5G CPE终端功能及性能评测(四)

5G CPE 功能性能评测 本文选取了几款在工业应用领域应用较多的5G CPE,对其功能和性能进行了对比评测。功能方面主要对比了网络接口数量,VPN功能 支持情况。以下测试为空口测试,测试结果受环境影响较大,性能仅供参考。总体看,高通X55芯片下行最优,速率稳定。 功能 对比CPE…...