# React Hooks 简介 (opens new window)
React Hooks
就是用函数的形式代替原来的继承类的形式,并且使用预函数的形式管理state
,有Hooks可以不再使用类的形式定义组件了。这时候你的认知也要发生变化了,原来把组件分为有状态组件和无状态组件,有状态组件用类的形式声明,无状态组件用函数的形式声明。那现在所有的组件都可以用函数来声明了。
# 使用步骤
使用create-react-app
创建项目
npm create-react-app i -g ## 安装脚手架
create-react-app [deom_name] ## 创建项目
Creating a new React app in D:\Learning_code\06_高级框架&技术\React-hooks\react-hooks.
cnpm i react@16.9 react-dom@16.9 --save ## 更新版本
# class 组件写法
import React, { Component } from 'react';
class Example extends Component {
constructor(props) {
super(props);
this.state = { count:0 }
}
render() {
return (<>
<p>You clicked {this.state.count} times</p>
<button onClick={this.addCount.bind(this)}>Chlick me</button>
</>);
}
addCount(){ // 每次加一, 需访问this.state
this.setState({count:this.state.count+1})
}
}
export default Example;
# Hooks 写法
import React, { useState } from 'react';
function CountHooks(){
const [ count , setCount ] = useState(0);
return (<>
<p>You clicked {count} times</p>
<button onClick={()=>{setCount(count+1)}}>click me</button>
</>)
}
export default CountHooks;
# 必须在根函数中定义
import React, { useState } from 'react';
let showSex = true
function Example(){
// 声明了count状态, 接受了值与设置值的方法
const [ age, setAge ] = useState(18);
if (showSex) { // 报错
const [ sex, setSex ] = useState('男');
showSex = false;
}
const [ work, setWork ] = useState('前端工程师');
return (<>
<p>Mr_Mao 今年:{age} </p>
<p>性别:{sex} </p>
<p>工作是:{work} </p>
</>)
}
export default Example;
# useEffect 声明生命周期的钩子
在使用React Hooks
的情况下,我们可以使用useEffect
完成上边代码的生命周期效果。
import React, { useState, useEffect } from 'react';
function CountHooks(){
const [ count, setCount ] = useState(0);
//---关键代码---------start-------
useEffect(()=>{
console.log(`useEffect=>You clicked ${count} times`)
})
//---关键代码---------end-------
return (<>
<p>You clicked {count} times</p>
<button onClick={()=>{setCount(count+1)}}>click me</button>
</>)
}
export default CountHooks;
React首次渲染和之后的每次渲染都会调用一遍useEffect
函数,useEffect
useEffect中定义的函数的执行不会阻碍浏览器更新视图,也就是说这些函数时异步执行的,而componentDidMonut
和componentDidUpdate
中的代码都是同步执行的。
# useEffect 组件卸除时
每次组件更新时,都会先试着清除上次的副作用(useEffect中的return function
)
import React, { useState, useEffect } from 'react';
function CountHooks(){
const [ count, setCount ] = useState(0);
//---关键代码---------start-------
useEffect(()=>{
console.log(`useEffect=>You clicked ${count} times`)
return ()=>{
// 返回一个副作用的解绑函数
}
})
//---关键代码---------end-------
return (<>
<p>You clicked {count} times</p>
<button onClick={()=>{setCount(count+1)}}>click me</button>
</>)
}
export default CountHooks;
useEffect 还有第二个数组参数,用来指定所监听的 state 列表,该参数缺省的话默认应该是全监听的,明确提供空数组时,每次组件更新都不会再执行该“副作用”,只有最终组件卸载时,react 会兜个底,调用解绑回调。
useEffect(()=>{
console.log(`useEffect=>You clicked ${count} times`)
return ()=>{/*组件销毁时执行*/}
}, [])
useEffect(()=>{
console.log(`useEffect=>You clicked ${count} times`)
return ()=>{/*count改变时*/}
}, [count])
# useEffect 写法对应生命周期
// 初次挂载(componentDidMount)
useEffect(() => {}, [])
// 组件卸载(componentWillUnmount)
useEffect(() => {
return () => {}
}, [])
// 自动收集更新(componentDidUpdate)
useEffect(() => {
// 这里的内部依赖会被收集
// 当 state 发生变化, 函数会被触发
})
# useContext 定义组件传参
在父子组件中传递,使用createContext与useContext传递父组件参数
import React, { useState, createContext, useContext } from 'react';
// 创建count上下文
const CountContext = createContext()
// 子组件
function Counter() {
let count = useContext(CountContext)
return <h2>{count}</h2>
}
// 父组件
function CountHooks() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => { setCount(count + 1) }}>click me</button>
{/* 创建一个上下文组件, value存放需要传递的值, 标签内传入组件 */}
<CountContext.Provider value={count}>
<Counter />
</CountContext.Provider>
</div>
)
}
export default CountHooks;
# useReducer 状态库的定义
import React, { useReducer } from 'react';
function ReducerDemo() {
// 定义一个状态, useReducer参数一是Reducer的状态函数, 第二个参数是该状态的默认值
const [count, dispatch] = useReducer((state, action) => {
switch (action) {
case 'add':
return state + 1
case 'sub':
return state - 1
default: return state;
}
}, 0)
return <>
<div>
<h2>现在的分数是{count}</h2>
{/* 点击进行派发, 改变count的状态 */}
<button onClick={() => dispatch('add')}>Increment</button>
<button onClick={() => dispatch('sub')}>Decrement</button>
</div>
</>
}
export default ReducerDemo
# Reducer与Context实现全局状态管理
useReducer 主要实现了状态管理采用Reducer模式管理,Context 主要实现了多个组件中数据的传递。两个结合起来就能实现一个全局的状态数据管理。
import React, { createContext, useReducer } from 'react'
// 定义一个全局上下文组件
export const ColorContext = createContext({})
// 定义action改变状态的常量
export const UPDATE_COLOR = "UPDATE_COLOR"
// 定义一个纯函数reducer状态库
const reducer = (state,action)=>{
switch(action.type){
case UPDATE_COLOR:
return action.color
default:
return state
}
}
export const Color = props => {
const [color, dispatch] = useReducer(reducer, "blue")
return <>
{/* 全局上下文中传入对象, 该对象可全局中每个组件使用useContext引用 */}
{/* 传入使用useReducer定义的状态与派发方法, 形成全局都能看到与调用的状态库 */}
<ColorContext.Provider value={{ color, dispatch }}>
{props.children}
</ColorContext.Provider>
</>
}
# useMemo 解决渲染性能问题
useMemo
主要用来解决使用React hooks产生的无用渲染的性能问题。使用function的形式来声明组件,失去了shouldCompnentUpdate
(在组件更新之前)这个生命周期,也就是说我们没有办法通过组件更新前条件来决定组件是否更新。而且在函数组件中,也不再区分mount
和update
两个状态,这意味着函数组件的每一次调用都会执行内部的所有逻辑,就带来了非常大的性能损耗。
import React, { useState, useMemo } from 'react';
function Example() {
const [xiaohong, setXiaohong] = useState('小红待客状态')
const [zhiling, setZhiling] = useState('志玲待客状态')
return <>
<button onClick={() => { setXiaohong(new Date().getTime()) }}>小红</button>
<button onClick={() => { setZhiling(new Date().getTime() + ',志玲向我们走来了') }}>志玲</button>
<ChildComponent name={xiaohong}>{zhiling}</ChildComponent>
</>
}
function ChildComponent({ name, children }) {
function changeXiaohong(name) {
console.log('她来了,她来了。小红向我们走来了')
return name + ',小红向我们走来了'
}
// 每当父组件状态发生改变都会执行该方法, 这样会导致性能出现问题
const actionXiaohong = changeXiaohong(name)
return <>
<div>{actionXiaohong}</div>
<div>{children}</div>
</>
}
export default Example
这时候你会发现在浏览器中点击志玲
按钮,小红对应的方法都会执行,结果虽然没变,但是每次都执行,这就是性能的损耗。目前只有子组件,业务逻辑也非常简单,如果是一个后台查询,这将产生严重的后果。所以这个问题必须解决。
其实只要使用useMemo
,然后给她传递第二个参数,参数匹配成功,才会执行。代码如下:
function ChildComponent({name,children}){
function changeXiaohong(name){
console.log('她来了,她来了。小红向我们走来了')
return name+',小红向我们走来了'
}
const actionXiaohong = useMemo(()=>changeXiaohong(name),[name])
return (
<>
<div>{actionXiaohong}</div>
<div>{children}</div>
</>
)
}
这时在浏览器中点击一下志玲
按钮,changeXiaohong
就不再执行了。也节省了性能的消耗。从程序本身看到优化的作用。好的程序员对自己写的程序都是会进行不断优化的,这种没必要的性能浪费也是绝对不允许的,所以useMemo
的使用在工作中还是比较多的。
# useRef 获取DOM元素
useRef
在工作中虽然用的不多,但是也不能缺少。它有两个主要的作用:用useRef
获取React JSX中的DOM元素,获取后你就可以控制DOM的任何东西了。但是一般不建议这样来作,React界面的变化可以通过状态来控制。或用useRef
来保存变量,这个在工作中也很少能用到,我们有了useContext
这样的保存其实意义不大,但是这是学习,也要把这个特性讲一下。
import React, { useRef, useState, useEffect } from 'react';
function Example8() {
const inputEl = useRef(null)
const onButtonClick = () => {
inputEl.current.value = "Hello ,useRef"
console.log(inputEl)
}
//-----------关键代码--------start
const [text, setText] = useState('jspang')
const textRef = useRef()
useEffect(() => {
textRef.current = text;
console.log('textRef.current:', textRef.current)
})
//----------关键代码--------------end
return <>
{/*保存input的ref到inputEl */}
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>在input上展示文字</button>
<br />
<br />
<input value={text} onChange={(e) => { setText(e.target.value) }} />
</>
}
export default Example8
这时候就可以实现每次状态修改,同时保存到useRef
中了。也就是我们说的保存变量的功能。那useRef
的主要功能就是获得DOM和变量保存。
# 自定义hooks函数
其实自定义Hooks函数和用Hooks创建组件很相似,跟我们平时用JavaScript写函数几乎一模一样,可能就是多了些React Hooks
的特性,自定义Hooks函数偏向于功能,而组件偏向于界面和业务逻辑。由于差别不大,所以使用起来也是很随意的。如果是小型项目是可以的,但是如果项目足够复杂,这会让项目结构不够清晰。所以学习自定义Hooks函数还是很有必要的。
在实际开发中,为了界面更加美观。获取浏览器窗口的尺寸是一个经常使用的功能,这样经常使用的功能,就可以封装成一个自定义Hooks
函数,记住一定要用use开头,这样才能区分出什么是组件,什么是自定义函数。
编写一个每次修改状态的方法onResize
,这个方法使用useCallback
,目的是为了缓存方法(useMemo是为了缓存变量)。
import React, { useState ,useEffect ,useCallback } from 'react';
function useWinSize(){
const [ size , setSize] = useState({
width:document.documentElement.clientWidth,
height:document.documentElement.clientHeight
})
const onResize = useCallback(()=>{
setSize({
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
})
},[])
useEffect(()=>{
window.addEventListener('resize',onResize)
return ()=>{
window.removeEventListener('resize',onResize)
}
},[])
return size;
}
function Example9(){
const size = useWinSize()
return <div>页面Size:{size.width}x{size.height}</div>
}
export default Example9