使用hooks理由
- 高阶组件为了复用,导致代码层级复杂
- 生命周期的复杂
- 写成functional组件,无状态组件 ,因为需要状态,又改成了class,成本高
一、useState (修改状态的钩子函数)
- 语法:
const [state, setstate] = useState(initialState)
- 状态:
(1) 自带性能优化的机制,记住变量的状态
每一次修改状态值的时候,会拿最新要修改的值和之前的状态值做比较(基于Object.is做比较);
如果发现两次的值是一样的,则不会修改状态,也不会让视图更新。
如果它修改的状态值一直更新,则会一直调用!!
- 第一个值是存储状态的值,第二个是改变状态的唯一方法
- 基本数据类型修改状态
const [state, setstate] = useState(initialState) const [name, setname] = useState('小明') setname('小红')
- 复杂数据类型修改状态
const [list, setList] = useState(["aa","bb","cc"]) setList([...list,"dd"])
- 修改状态为函数类型
const [count, setCount] = useState(0) setCount(count => count+1) 注:不建议直接改变原来的状态,而是直接用改变状态的唯一方法去操作
setXxx(newValue): 参数为非函数值,直接指定新的状态值,内部用其覆盖原来的状态值
setXxx(value => newValue): 参数为函数,接收原本的状态值,返回新的状态值,内部用其覆盖原来的状态值
- 基本数据类型修改状态
二、useCallback (缓存函数)
- 语法:
useCallback(()=>{},[deps])
- 状态:
(1) 自带性能优化的机制,记住函数的状态
(2) 缓存创建的方法,防止因为组件的渲染,导致方法的重新创建,只有第二个参数变化了,才重新声明一次。let getName = useCallback(()=>{ console.log(name) },[name]) <button onClick={()=>getName()}>你好</button>
- [deps] 的影响:
- useCallback(() => {}, [ ] )此种情况,则useCallback函数只执行一次
- useCallback(() => {} ) 此种情况,则每次都重新声明一次,拿到的都是最新的deps
- useCallback(() => {},[name] )此种情况, 该参数存在且有deps,当name发生变化,该函数就会重新声明一次
三、useMemo (缓存组件)
- 语法:
useMemo(() => () => {},[deps])
- 状态:
(1) useMemo会执行第一个函数,并且将执行结果返回。
(2) useMemo可以取代useCallback
四、useEffect (副作用钩子)
- 语法:
useEffect(() => {}, [deps])
- deps:依赖,选填
- 传递空数组,只会执行一次
- 执行时机:
在组件渲染完成后执行,在类组件中会把请求放在componentDidMount里面,不会阻塞浏览器的渲染过程。 - 作用
- 在类组件中会把请求放在componentDidMount里面
- 在函数组件中可以使用useEffect()
- 设置订阅 / 启动定时器
- 发送ajax请求获取数据
- 手动更改真实dom
- [deps] 的影响:
- useEffect(() => {}, [ ] )此种情况,则effect函数只执行一次
- useEffect(() => {} ) 此种情况,则useEffect函数监视所有useState的数据,当数据发生改变,则useEffect函数就执行
- useEffect(() => {},[name] )此种情况, 该参数存在且有deps,当name发生变化,则useEffect函数执行
- 什么时候 [ ] 里面可以写值?
- 当修改的值,不止被改变一次的时候
export default function App() { const [name, setname] = useState("王五") useEffect(() => { setname(name.substring(0,1)) }, [name]) // 第一次执行一次, 之后name(依赖)更新也会执行 return ( <div> 我的名字是-------{name} <button onClick={()=>{ setname("张三") }}>click</button> </div> ) }
- 当修改的值,不止被改变一次的时候
- useEffect 开启定时器后,如何取消定时器?
const [num,setNum] = useState(1) useEffect(() => { let timerId = setInterval(() => { setNum(num => num+1 ) }, 1); return ()=>{ clearTimeout(timerId); // 组件卸载时清除定时器 } // useEffect 相当于componentDidMount, //useEffect函数返回的函数,相当于componentWillunMount }, [])
- 总结:
- 可以把 useEffect Hook 看做如下三个函数的组合
- componentDidMount ()
- componentDidUpdate()
- componentWillUnmount()
- 可以把 useEffect Hook 看做如下三个函数的组合
五、useLayoutEffect (同步执行副作用)
- 语法:
useLayoutEffect(() => {}, [deps])
- deps:依赖,选填
- 传递空数组,只会执行一次
- 执行时机:
在组件渲染完成后,浏览器绘制之前执行,可以看作是 useEffect 的同步版本。
useLayoutEffect 的回调函数在每次渲染结束后同步
执行,会阻塞浏览器的渲染过程。
可能导致页面性能下降 - 作用
- 避免页面抖动(在 useEffect 里修改DOM很有可能出现)的话,把操作dom的代码放入 useLayoutEffect ,操作dom的行为和 react 做出的更改,被一次性渲染到屏幕上,只有一次回流、重绘的代价。
六、useRef (保存引用值)
- 语法:
先定义ref={myref} , 后使用const myref = useRef()
- 使用:
- 表单类用法:
export default function App() { const mytext = useRef() const delete = ()=>{ mytext.current.value = "" } return ( <div> <input ref={mytext}/> <button onClick={delete}>清空</button> </div> ) }
- 非表单类用法:
- 如何保存变量?
export default function App() { var myAge = useRef(18) return ( <button onClick={()=>{ myAge.current ++ }}> 当前我的年龄为:{myAge} </button> ) }
七、useContext (一种组件间通信方式)
- 语法:
useContext()
- 使用:
- 创建Context容器对象:
const xxx = React.createContext()
- 渲染子组件时,外面包裹xxx.Provider, 通过value属性给后代组件传递数据:
<xxx.Provider value={数据}> 子组件 </xxx.Provider>
- 后代组件读取数据:
//第一种方式:仅适用于类组件 static contextType = xxxContext // 声明接收context this.context // 读取context中的value数据 //第二种方式: 函数组件与类组件都可以 <xxx.Consumer> { value => ( // value就是context中的value数据 要显示的内容 ) } </xxxContext.Consumer>
- 示例:
import React, { useState,useEffect,useContext } from 'react' // 类式组件context用法 // 爷爷给孙子零花钱和小汽车怎么给? const GlobalContext = React.createContext() //创建上下文对象 const { Provider,Consumer } = GlobalContext //解构 A组件================ export default class A extends React.Component { state = { money: "10元", car: "兰博基尼" } render () { const { money, car } = this.state return( <div className='a'> <h2>我是爷爷</h2> <Provider value={{money:money,car:car}}> <B/> </Provider> </div> ) } } B组件==================== class B extends A { static contextType = GlobalContext render () { return( <div className='b'> <h2>我是爸爸</h2> <h3>{GlobalContext.money}</h3> <C/> </div> ) } } C组件=============== 类式写法 class C extends B { static contextType = GlobalContext //谁声明,谁使用。 render () { return( <div className='c'> <h2>我是儿子</h2> <p>我的钱-{this.context.money}</p> <p>我的车-{this.context.car}</p> </div> ) } }
- 问:若c不是类式组件,怎么接收爷爷的钱和小汽车?
- 答: (第一种方法
Consumer
)function C(){ return( <div className='c'> <h2>我是儿子</h2> <Consumer> { value=>{ return `我的钱有:${value.money},我的车:${value.car}` } } </Consumer> </div> ) }
- 答: (第二种方法
useContext
)function C(){ const value = useContext(GlobalContext) return( <div className='c'> <h2>我是儿子</h2> {value.car} </div> ) }
八、useReducer (类似 redux 的功能 api )
- 语法:
const [state, dispatch] = useReducer(reducer,intialState)
reducer //函数, intialState //对象
适合做复杂父子通信
九、 render props
- 向组件内部动态传入带内容的结构(标签)
- Vue中:
- 使用slot技术, 也就是通过组件标签体传入结构
-
- React中:
- 使用children props: 通过组件标签体传入结构
- 使用render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性
- React中:
- Vue中:
十、 Fragment (文档碎片)
- 语法
<Fragment><Fragment> <></>
- 作用
- 如果在html标签包裹错误时,渲染的静态html会出现问题,借助Fragment,可以解决这个问题, 具体可看 https://cloud.tencent.com/developer/article/1923532
- 可以不用必须有一个真实的DOM根标签,可以用Fragment或者<>代替。