hook

397 阅读3分钟

小知识,大挑战!本文正在参与“  程序员必备小知识  ”创作活动

本文同时参与 「掘力星计划」  ,赢取创作大礼包,挑战创作激励金

为什么要使用Hook

优化了类组件的三大问题

  1. 方便复用状态逻辑 类组件渲染属性(prop render)和高阶组件导致层级冗余,通过自定义hook可以很方便达到复用 1. HOC:固定的props可能被覆盖(重名) 2. render props: 无法在return语句外访问数据;用到多个时会嵌套地狱 3. hook:可以重命名;变量来源很清楚,方便定位问题;可以在return之外 使用数据;hook不会嵌套
  2. 副作用的关注点分离(一个hook单纯只做一件事,不像生命周期会混合(又是设置title,又获取接口))

副作用:数据渲染视图之外的功能,比如设置title,获取数据

  1. 函数组件无this问题

常用hook函数

useState

语法

const [position, setPosition] = useState({ x: 0, y: 0 })

useState参数为初始值,会返回数组,第一项为state值,第二项为更改state的方法

注意点

  1. 稳定顺序和数量
  2. useState不像setState会合并原有的state,比如
const [obj,setObj] = useState({like:0,switch:false})
//setObj({like:obj.like+1}) //错误的
setObj({like:obj.like+1,switch:obj.switch});
  1. 每个state声明个useState
const [like, setLike] = useState(0)
const [status, toggleStatus] = useState(false)

useEffect

语法

第一个参数为函数,如果有返回函数表示清除函数,第二个参数是个数组,表示执行时间(数组里任何一项更改时就会触发)

useEffect(() => {
	document.title = `点击${like}次`
}, [like, status])
useEffect(() => {
	window.addEventListener('resize',onResize,false);
	return () => {
		window.removeEventListener('resize',onResize)
	}
},[]) //因为为空数组,永远不会改变,所以useEffect只会执行一次

注意点

  1. 先执行上一次的清除函数,再执行本次的副作用

自定义hook

语法

  1. 以use开头的函数

示例

自定义个hook实现loading

import { useState, useEffect } from 'react'
import axios from 'axios'

const useUrlLoader = (url: string, deps: any[] = []) => {
	const [loading, setLoading] = useState(false)
	const [data, setData] = useState<any>(null)
	useEffect(() => {
		setLoading(true)
		axios.get(url).then((result) => {
			setData(result.data)
			setLoading(false)
		})
	}, deps)
	return [data, loading]
}

export default useUrlLoader
interface IShowResult {
	message: string
	status: string
}
function App() {
	const [
		data,
		loading
	] = useURLLoader('https://dog.ceo/api/breeds/image/random', [show])
	const dogResult = data as IShowResult
	return (
		<div className="App">
		{/* {loading ? <p>读取中</p> : <img src={dogResult.message} />}  */}
		{/* 上面一行没加dogResult判空处理,是有问题的 */}
		{loading ? <p>读取中</p> : <img src={dogResult && dogResult.message} />}
		</div>
	)
}

useMemo

要么就是存一个对象 prop,要么就是存一个 JSX

useRef

由于每次渲染周期获取到的数据都是本次的,而要达到跨渲染周期就需要采用useRef。另外要获取DOM节点或者子节点在函数里对应的ref方式就是useRef。useRef会在每次渲染时返回同一个 ref 对象

无法跨渲染周期

const LikeButton: React.FC = () => {
	const [like, setLike] = useState(0)
	function handleAlertClick() {
		setTimeout(() => {
			alert(`you clicked on ${like}`) 
			//形成闭包,所以弹出来的是当时触发函数时的like值
		}, 3000)
	}
	return (
		<>
			<button onClick={() => setLike(like + 1)}>{like}赞</button>
			<button onClick={handleAlertClick}>Alert</button>
		</>
	)
}
export default LikeButton

采用useRef修订(用法1:直接访问.current)

  1. 采用useRef定义
const likeRef = useRef(0)
  1. 由于每次渲染时返回同一个ref对象,所以通过设置以及获取likeRef.current
const LikeButton: React.FC = () => {
	const [like, setLike] = useState(0)
	// useRef 会在每次渲染时返回同一个 ref 对象
	const likeRef = useRef(0)
	function handleAlertClick() {
		setTimeout(() => {
			// 1.ref直接访问.current
			alert(likeRef.current)
		}, 3000)
	}
	return (
		<>
			<button
				onClick={() => {
					setLike(like + 1)
					likeRef.current++
				}}
			>
				{like}赞
			</button>
			<button onClick={handleAlertClick}>Alert</button>
		</>
	)
}
export default LikeButton

用法2:利用ref的全局性,区分出来是初始化还是更新

const didMountRef = useRef(false)
useEffect(() => {
	// 2.利用ref的全局性,区分出来是初始化还是更新
	if (didMountRef.current) {
		console.info('this is updated')
	} else {
		didMountRef.current = true
	}
})

用法3:访问DOM元素

const LikeButton: React.FC = () => {
	const [like, setLike] = useState(0)
	const domRef = useRef<HTMLParagraphElement>(null)
	function handleAlertClick() {
		setTimeout(() => {
			if (domRef && domRef.current) {
				//3.ref用在DOM上demo
				alert(domRef.current.innerText) 
				//全局的,就是当前的like值 可以“跨渲染周期”保存数据
			}
		}, 3000)
	}
	return (
		<>
			<button onClick={() => setLike(like + 1)}>{like}赞</button>
			<p ref={domRef}>{like}</p>
			<button onClick={handleAlertClick}>Alert</button>
		</>
	)
}
export default LikeButton

useContext

useContext(MyContext)等价于类组件里的<MyContext.Consumer> 以及静态static contextType = MyContext

定义Provider

interface IThemeProps {
	[key: string]: { color: string; background: string }
}
const thems: IThemeProps = {
	light: {
		color: '#000',
		background: '#eee'
	},
	dark: {
		color: '#fff',
		background: '#222'
	}
}
export const ThemeContxt = React.createContext(thems.light)

tsx部分

<ThemeContxt.Provider value={thems.dark}>
...
</ThemeContext.Provider>

定义Consumer

const LikeButton: React.FC = () => {
	const [like] = useState(0)
	const theme = useContext(ThemeContxt)
	const style = {
		color: theme.color,
		background: theme.background
	}
	return <button style={style}>{like}赞</button>
}
export default LikeButton

useLayoutEffect与useEffect区别

  1. useEffect是在浏览器将所有变化渲染到屏幕上之后异步执行,而useLayoutEffect是在DOM变更之后同步执行
  2. 代码层面: useEffect是在beforeMutation调度,layout注册,commit之后异步执行; 而useLayoutEffect是在mutation执行destroy,在layout执行create

遇到问题

初始化个typescript React项目 当使用npx create-react-app react-with-typescript --typescript生成的项目不是typescript时,可以用npx create-react-app react-with-typescript --template typescript 试下