|
Hook 是 React 16.8 的新增特性,它可以让我们在不编写 class 的情况下使用 state 以及其他的 React 特性 (比如生命周期)。
Hook 出现之前,组件之间复用状态逻辑很难,解决方案(HOC、Render Props)都需要重新组织组件结构, 且代码难以理解。在React DevTools 中观察过 React 应用,你会发现由 providers,consumers,高阶组件,render props 等其他抽象层组成的组件会形成“嵌套地狱”。 组件维护越来越复杂,譬如事件监听逻辑要在不同的生命周期中绑定和解绑,复杂的页面componentDidMount包涵很多逻辑,代码阅读性变得很差。 class组件中的this难以理解,且class 不能很好的压缩,并且会使热重载出现不稳定的情况。 所以hook就为解决这些问题而来:
使用说明:
1.1 useState() 用于定义函数组件的 State。 useState 接受唯一的参数作为 state 的初始值,不设置则为 undefined。 useState 调用后返回一个数组,包含两个元素,第一个值为 state 读取值,第二个值为改变 state 的函数 // 使用 Hook 的函数组件
import { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}> Click me </button>
</div>
);
}
// 等价的 class 组件
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}useState 的参数可以是一个函数: const [counter, setCount] = useState(() => 10) // 这里只会在初始化的时候执行 setCount 的参数也可以是一个函数: setCount(prevState => prevState + 10) // 累加 // 这里面 setCount 方法与 class 中的 setState 不同在于,此 setCount 不会合并 state 中的值 如果想要在 state 中使用一个对象需要在更新值的时候把之前的值解构出来: const [state, setState] = React.useState({ count: 0, greeting: "Hello, World!", }); // ... setState({ ...state, count: state.count + 1 }) 注:如果需要定义多个 state 只需要多次调用 useState 方法就行。 1.2 useEffect(callback, [source]) 可以看做 class 组件中 componentDidMount、componentDidUpdate、和 componentWillUnmount 几个生命周期的大集合。给函数组件增加了操作副作用的能力(网络请求、更新 DOM、事件的监听)。 useEffect 接受两个参数,第一个参数是一个 callback,可以用来做一些副作用比如异步请求,修改外部参数等行为。 import { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}> Click me </button>
</div>
);
}如果 callback 函数中 return 了一个函数,这个函数会在组件卸载前执行,需要清除上次订阅的内容可以再这里面写。它会在调用一个新的 effect 之前对前一个 effect 进行清理,所以 effect 的清除阶段在每次重新渲染时都会执行,而不是只在卸载组件的时候执行一次。useEffect(() => {
console.log('订阅一些事件')
return () => { // 组件卸载后执行 返回的函数
console.log('取消订阅')
}
})useEffect 的第二个参数 source 是一个触发条件,数组类型,如果数组中的值变化才会触发 useEffect 的 callback 回调。
useEffect(() => {
document.title = `You clicked ${count} times`;
}, []);避免在 useEffect 中做太多事情,如果有多个依赖可能会发生变化时,直接分离 useEffect,会按照先后顺序执行。 function Post({ id, unlisted }) {
useEffect(() => {
fetch(`/posts/${id}`).then( )
}, [id])
useEffect(() => {
setVisibility(unlisted)
}, [unlisted])
}使用 useEffect 传入依赖 时,有些依赖每次渲染都会重新生成一个引用,但是内部的值却没变,导致 effect 的无限更新。// 如
const getDep = () => {
return {
foo: 'bar',
};
};
useEffect(() => {
// 无限循环了
}, [getDep()]);
// 解决方案:把依赖转为字符串
const getDep = () => {
return {
foo: 'bar',
};
};
const dep = JSON.stringify(getDeps());
useEffect(() => {
// ok!
}, [dep]);
// 最好还是用社区提供的方案:useDeepCompareEffect,它选用深比较策略1.3 useLayoutEffect 跟 useEffect 函数签名一致,是在 DOM 修改后同步触发;并且总是比 useEffect 先执行,会阻塞浏览器的渲染。 useEffect 执行顺序: 组件更新挂载完成 -> 浏览器 dom 绘制完成 -> 执行 useEffect 回调。 useLayoutEffect 执行顺序: 组件更新挂载完成 -> 执行 useLayoutEffect 回调-> 浏览器dom绘制完成。 const [count, setCount] = useState(0);
useEffect(() => { // 会有闪烁
console.log(`useEffect - count=${count}`)
// 耗时的操作
const pre = Date.now();
while(Date.now() - pre < 1000) {}
// count为 0 时重新生成个随机数
if (count === 0) {
setCount(10 + Math.random() * 200);
}
}, [count]);
useLayoutEffect(() => { // 不会有闪烁,但是会卡一下
console.log(`useLayoutEffect - count=${count}`)
const pre = Date.now();
while(Date.now() - pre < 1000) {}
if (count === 0) {
setCount(10 + Math.random() * 200);
}
}, [count]);
<div onClick={() => setCount(0)}>{count}</div>当在 useEffect 里面的操作需要处理 DOM,并且会改变页面的样式,就需要用 useLayoutEffect 替换 useEffect,否则可能会出现闪屏问题。1.4 useRef 用于函数组件中获取 dom 元素。 import React, { useRef, useEffect } from 'react'
const HooksComponent = () => {
const inputRef = useRef(null);
useEffect(() => {
console.log(inputRef.current.value);
}, [])
return <input ref={inputRef} />
}
总结文件:https://blog.csdn.net/aliven1/article/details/120344030 1.5 useImperativeHandle forwardRef案例: 子组件: const Foo = forwardRef((params) => {
const inputRef = useRef();
const onclick= (params) => {inputRef.current.focus();}
return (
<div>
<input type="text" ref={inputRef }/>
<button onClick={onclick}>聚焦</button></div>
)
)父组件: const App= (params) =>{
const inputRef= useRef();
const onclick = (params) =>{
console.log(inputRef.current);
retun (
<div>
<Foo ref= {inputRef} />
<button onclick={onclick}>父组件</button></div>
)父组件去使用子组件中的方法 export default function App() {
const hello = useRef(null)
const handler = ()=>{//拿到hello内部的方法
hello.current.add()
}
return (
<div>
<Button onclick={ handler}>触发Hello组件中的方法</Button>
<Hello ref={hello}/>
</div>
)
}useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。useImperativeHandle 在当前组件 render 后执行。// 语法 useImperativeHandle(ref, createHandle, [deps]) // * ref:需要传递的 ref。 // * createHandle: 需要暴露给父级的方法。 // * deps: 依赖。 注意: 没有deps,每当rerender时,useImperativeHandle 都会执行, 且能拿到 state中最新的值, 父组件调用传入的方法也是最新。 依赖[],每当rerender时,useImperativeHandle 不会执行,且不会更新到父组件。 依赖传入的state值 [value], 达到想要的效果。 |
|
|