useState
useState 返回的更新对象的方法是`异步的`,要在下次重绘才能获取新值,不要试图在更改状态之后立即获取状态。
const [state, setState] = useState(initialState);
参数(initValue)
: 第一次初始化指定的值在内部作缓存
返回值([xxx, setXxx])
: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
setXxx()2种写法:
setXxx(newValue)
: 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
setXxx(value => newValue)
: 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
我们使用两种方式来进行一个对比:
import React, { useState } from 'react'
export default function LoadingClass() {
const [num, setNum] = useState(0)
const handleClick = () => {
setTimeout(() => {
setNum(num => num + 1)
}, 1000);
}
const handleClick2 = () => {
setTimeout(() => {
setNum(num + 1)
}, 1000);
}
return (
<div>
{num}
<br />
<button onClick={handleClick}>+1</button>
<button onClick={handleClick2}>不使用函数方式</button>
</div>
)
}
我们快速点击可以发现一个现像:
使用函数为参数可以一直递增,使用非函数的方式,多次点击的结果为1
为什么呢?
① 传入值的情况:多次调用时可能会被合并成一次更新
,导致只触发了一次渲染。
②对于传入函数的方式,在调用 setState 进行更新 state 时,React 会按照各个 setState 的调用顺序,将它们依次放入一个队列,然后,在进行状态更新时,则按照队列中的先后顺序依次调用
,并将上一个调用结束时产生的 state 传入到下一个调用的函数中
useEffect
副作用钩子,用于处理组件中的副作用,用来取代生命周期函数。所谓的"副作用"就是指的是组件中状态或生命周期改变时在useEffect可监听到。
可以将useEffect视作coponentDidMount、componentDidUpdate和componentWillUnmount
的组合体。
几种情况:
- 无此参数:组件的任何更新,该 useEffect 对应的返回函数和函数都执行
- 为空数组:只在componentDidMount执行一次,不监听组件的更新,
- 数组中有具体依赖:对应的依赖数据,有变化的时候,才会执行(初始不会执行)
- 返回一个函数在componentWillUnmount的时候执行,比如清除定时器等
useEffect(()=>
//写对应逻辑即可
return ()=>{// componentWillUnmount执行
}
},[])
useReducer
const [state, dispatch] = useReducer(reducer, initialState);
- 应用于state复杂变化
- 单个组件状态管理,组件通讯还需要props
- redux时全局状态管理,多组件共享数据
const initialState = {count: 0};
const reducer = (state, action) => {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
};
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
useMemo
比较类似vue的计算属性
,当依赖值变化才重新计算。
如果依赖项数组为空,useMemo 只会在组件首次渲染时计算 memoized 值,后续渲染将重用这个值,而不进行重新计算。
用于在组件重新渲染时避免不必要的计算和渲染。它会返回一个 memoized 值,这个值只会在依赖项发生变化时重新计算。
const memoizedValue = useMemo(()=>{return {a:a}},[a])
React.memo
写在这里进行一个对比
React.memo 通过对组件的 props
进行浅比较来实现性能优化。
- react默认更新所有子组件
- class使用
SCU
和PureComponent
做优化 - Hooks使用
useMemo
,但优化的原理是相同的,对组件进行浅层对比。
React.memo(组件,参数2)
React.memo参数2返回值如果为true则表示停止渲染,false继续渲染
const Child = memo(
({ count }) => {
console.log('child')
return (
<div>
<h3>child组件 -- {count.n}</h3>
</div>
)
},
// 使用lodash库来完成对象的值的比较,从而用来完成减少组件的无用的重复渲染
(prevProps, nextProps) => _.isEqual(prevProps, nextProps)
)
//如果不写第二个参数,那么此时子组件只依赖于父组件中的 count,所以父组件中的count改变,才会重新渲染子组件
useCallback
useCallback是React中的一个钩子函数,用于优化函数组件的性能。它的作用是在依赖项不变的情况下,避免函数的重复创建。useCallback 要配合子组件的 shouldComponentUpdate 或者 React.memo 一起来使用的,否则就是反向优化。
通常情况下,当父组件重新渲染时,所有在父组件中定义的函数都会重新创建。如果将这些函数作为props传递给子组件,可能会导致子组件的不必要重新渲染。
使用useCallback可以缓存函数引用,只有在依赖项发生变化时才会重新创建函数。这样可以确保只有在需要时才会更新子组件。
const memoizedCallback = useCallback(
() => {
// 函数体
},
[依赖项数组]
);
第一个参数是要缓存的函数,第二个参数是一个依赖项数组。当依赖项数组中的任何一个值发生变化时,memoizedCallback函数将被重新创建。如果依赖项数组为空,memoizedCallback函数将永远不会更新。
useCallBack的本质工作不是在依赖不变的情况下阻止函数创建,而是在依赖不变的情况下不返回新的函数地址而返回旧的函数地址
。不论是否使用useCallBack都无法阻止组件render时函数的重新创建!
每一个被useCallBack的函数都将被加入useCallBack内部的管理队列。而当我们大量使用useCallBack的时候,管理队列中的函数会非常之多,任何一个使用了useCallBack的组件重新渲染的时候都需要去便利useCallBack内部所有被管理的函数找到需要校验依赖是否改变的函数并进行校验。
为啥要使用?
当传递一个函数给子组件,父组件更改某个值的时候,重新构建的时候,会重新构建父组件中的所有函数(旧函数销毁,新函数创建,等于更新了函数地址),新的函数地址传入到子组件中被props检测到栈地址更新,则子组件进行更新,为了避免不必要的更新可以使用useCallback
缓存,不更改该方法的地址,此时子组件不会进行更新。
一些问题
- 为什么使用Hooks
-
完善函数组件的能力,
-
函数更适合react组件 组件逻辑复用,hooks表现更好
-
class复杂组件正在变得不好拆解,不易测试,逻辑混乱(相同逻辑散落在各处(DidMount和DidUpdate中获取数据,DidMount绑定事件,WillUnMount解绑事件,在hooks中可以放在useEffet中)
-
hooks遇到的坑
-
useState初始化值,只初始化一次
-
useEffect内部,不能修改state
-
useEffect依赖引用类型,会出现死循环
-
做组件复用的优点
符合hooks原有规则
变量作用域明确
不会产生组件嵌套
高阶组件 HOC
高阶组件(Higher-Order Component,缩写为HOC)是一种在React中用于复用组件逻辑的技术。它本质上是一个函数,接受一个组件作为参数,并返回一个新的组件。
HOC可以用来增强现有组件的功能
,例如添加状态管理、处理认证逻辑、日志记录等。通过将通用的逻辑封装在HOC中,我们可以在不修改原始组件的情况下对其进行扩展
。这样做的好处是可以提高代码的可重用性和可维护性。
使用高阶组件的过程大致如下:
- 创建一个函数,将需要增强的组件作为参数传入。
- 在函数内部创建一个新的组件,并在其中实现所需的增强逻辑。
- 返回新创建的组件作为结果。
// 定义一个高阶组件
function withHover(Component) {
return class WithHover extends React.Component {
constructor(props) {
super(props);
this.state = { isHovered: false };
}
handleMouseEnter = () => {
this.setState({ isHovered: true });
};
handleMouseLeave = () => {
this.setState({ isHovered: false });
};
render() {
const { isHovered } = this.state;
return (
<div
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
>
<Component {...this.props} isHovered={isHovered} />
</div>
);
}
};
}
// 创建一个普通组件
function Button({ isHovered }) {
return (
<button>{isHovered ? 'Hovered' : 'Not Hovered'}</button>
);
}
// 使用高阶组件来增强普通组件
const ButtonWithHover = withHover(Button);
// 渲染增强后的组件
ReactDOM.render(<ButtonWithHover />, document.getElementById('root'));
在上面的示例中,withHover函数是一个高阶组件,它接受一个组件作为参数,并返回一个增强后的新组件。通过调用withHover(Button),我们创建了一个名为ButtonWithHover的新组件,它具有鼠标悬停功能。
class组件
在 React 18 中,class组件中处理this的指向
- 使用箭头函数定义事件处理程序:
class MyComponent extends React.Component {
handleClick = () => {
// 处理点击事件逻辑
}
render() {
return (
<button onClick={this.handleClick}>点击我</button>
);
}
}
- 使用普通函数定义事件处理程序并在构造器中绑定 this:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
// 处理点击事件逻辑
}
render() {
return (
<button onClick={this.handleClick}>点击我</button>
);
}
}