React 中提供的 hooks:
- useState:setState
- useReducer:setState,同时 useState 也是该方法的封装
- useRef: ref
- useImperativeHandle: 给 ref 分配特定的属性
- useContext: context,需配合 createContext 使用
- useMemo: 可以对 setState 的优化
- useCallback: useMemo 的变形,对函数进行优化
- useEffect: 类似 componentDidMount/Update, componentWillUnmount,当效果为 componentDidMount/Update 时,总是在整个更新周期的最后(页面渲染完成后)才执行
- useLayoutEffect: 用法与 useEffect 相同,区别在于该方法的回调会在数据更新完成后,页面渲染之前进行,该方法会阻碍页面的渲染
- useDebugValue:用于在 React 开发者工具中显示自定义 hook 的标签
官网地址:https://react-1251415695.cos-website.ap-chengdu.myqcloud.com/docs/hooks-reference.html#basic-hooks
一、Hooks 初体验
example:
import React, { useState } from 'react'; function Example() { // 声明一个名为“count”的新状态变量 const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } export default Example;
useState
就是一个 Hook
,可以在我们不使用 class
组件的情况下,拥有自身的 state
,并且可以通过修改 state
来控制 UI 的展示。
1、useState状态
语法:
const [state, setState] = useState(initialState)
- 传入唯一的参数:
initialState
,可以是数字,字符串等,也可以是对象或者数组。 - 返回的是包含两个元素的数组:第一个元素,
state
变量,setState
修改 state值的方法。
与在类中使用 setState
的异同点:
- 相同点:在一次渲染周期中调用多次
setState
,数据只改变一次。 - 不同点:类中的
setState
是合并,而函数组件中的setState
是替换。
使用对比
之前想要使用组件内部的状态,必须使用 class 组件,例如:
import React, { Component } from 'react'; export default class Example extends 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> ); } }
而现在,我们使用函数式组件也可以实现一样的功能了。也就意味着函数式组件内部也可以使用 state 了。
import React, { useState } from 'react'; function Example() { // 声明一个名为“count”的新状态变量 const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } export default Example;
优化
创建初始状态是比较昂贵的,所以我们可以在使用 useState
API 时,传入一个函数,就可以避免重新创建忽略的初始状态。
普通的方式:
// 直接传入一个值,在每次 render 时都会执行 createRows 函数获取返回值
const [rows, setRows] = useState(createRows(props.count));
优化后的方式(推荐):
// createRows 只会被执行一次const [rows, setRows] = useState(() => createRows(props.count));
2、useEffect执行副作用操作
- effect(副作用):指那些没有发生在数据向视图转换过程中的逻辑,如
ajax
请求、访问原生dom
元素、本地持久化缓存、绑定/解绑事件、添加订阅、设置定时器、记录日志等。 - 副作用操作可以分两类:需要清除的和不需要清除的。
- 需要清除的,比如开启的定时器,订阅外部数据源等,这些操作如果在组件消亡后不及时清除会导致内存泄漏。
- 不需要清除的,比如发起网络请求,手动变更 DOM,记录日志等。
- 原先在函数组件内(这里指在 React 渲染阶段)改变 dom 、发送 ajax 请求以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的 bug 并破坏 UI 的一致性
- useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的
componentDidMount
、componentDidUpdate
和componentWillUnmount
具有相同的用途,只不过被合并成了一个 API - useEffect 接收一个函数,该函数会在组件渲染到屏幕之后才执行,该函数有要求:要么返回一个能清除副作用的函数,要么就不返回任何内容
- 与
componentDidMount
或componentDidUpdate
不同,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的 useLayoutEffect Hook 供你使用,其 API 与 useEffect 相同。
语法:
1、useEffect(() => { doSomething }); 2、useEffect(() => { doSomething },[]); 3、useEffect(() => { doSomething },[count]);
第一个参数为 effect 函数,该函数将在 componentDidMmount 时触发和 componentDidUpdate 时有条件触发(该添加为useEffect 的第二个数组参数)
- 第二个参数是可选的,根据条件限制看是否触发
- 如果不传,如语法1,则每次页面数据有更新(如componentDidUpdate),都会触发 effect。
- 如果为空数组
[]
,如语法2,则每次初始化的时候只执行一次effect(如componentDidMmount) - 如果只需要在指定变量变化时触发 effect,将该变量放入数组。如语法3,count只要变化,就会执行effect,如观察者监听
- 清除副作用
- 副作用函数还可以通过返回一个函数来指定如何清除副作用,为防止内存泄漏,清除函数会在组件卸载前执行。如果组件多次渲染,则在执行下一个 effect 之前,上一个 effect 就已被清除。
- 例1、比如window.addEventListener('resize', handleResize);:监听resize等
useEffect(() => { window.addEventListener('resize', handleResize); window.addEventListener('keydown', onKeyDown); window.addEventListener('keyup', onKeyUp); return (() => { window.removeEventListener('resize', handleResize); window.removeEventListener('keydown', onKeyDown); window.removeEventListener('keyup', onKeyUp); }) }, [globalRef]);
例2、清除定时器
function Counter(){ let [number,setNumber] = useState(0); let [text,setText] = useState(''); useEffect(()=>{ let $timer = setInterval(()=>{ setNumber(number=>number+1); },1000); // useEffect 如果返回一个函数的话,该函数会在组件卸载和更新时调用 // useEffect 在执行副作用函数之前,会先调用上一次返回的函数 // 如果要清除副作用,要么返回一个清除副作用的函数 /* return ()=>{ console.log('destroy effect'); clearInterval($timer); } */ }); // },[]);//要么在这里传入一个空的依赖项数组,这样就不会去重复执行 return ( <> <input value={text} onChange={(event)=>setText(event.target.value)}/> <p>{number}</p> <button>+</button> </> ) }
3、useContext组件之间传值
语法
const value = useContext(MyContext);
之前在用类声明组件时,父子组件的传值是通过组件属性和props进行的,那现在使用方法(Function)来声明组件,已经没有了constructor构造函数也就没有了props的接收,但是也可以直接收,如下:
组件: <SwitchList dataList={toolsList} isReverse={false}/> 接收: const SwitchList = ({dataList = null, isReverse = false}: any): React.ReactElement => { //TODO }
React Hooks 也为我们准备了useContext。它可以帮助我们跨越组件层级直接传递变量,实现共享。
一:利用 createContext 创建上下文
import React, { useState , createContext } from 'react'; // 创建一个 CountContext const CountContext = createContext() function Example(){ const [ count , setCount ] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={()=>{setCount(count+1)}}>click me</button> {/* 将 context 传递给 子组件,context 值由value props决定*/} <CountContext.Provider value={count}> <Counter/> </CountContext.Provider> </div> ) } export default Example;
二:使用useContext 获取上下文
对于要接收context的后代组件,只需引入 useContext() Hooks 即可。
强调一点:
useContext 的参数必须是 context 对象本身:
- 正确:useContext(MyContext)
- 错误:useContext(MyContext.Consumer)
- 错误:useContext(MyContext.Provider)
当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 <MyContext.Provider> 的 context value 值。