学习目标:能够理解并能够使用hooks
目录
三、useEffect——为react函数组件提供副作用处理
六、useContext ——在hooks下的context使用方式
一、Hooks概念
Hooks的本质:一套能够使函数组件更强大,更灵活的“钩子”
React体系里组件分为 类组件 和 函数组件
经过多年的实战,函数组件是一个更加匹配React的设计理念 UI = f(data)
,也更有利于逻辑拆分与重用的组件表达形式,而先前的函数组件是不可以有自己的状态的,为了能让函数组件可以拥有自己的状态,所以从react v16.8开始,Hooks应运而生
1、注意点:
- 有了hooks之后,为了兼容老版本,class类组件并没有被移除,俩者都可以使用
- 有了hooks之后,不能在把函数成为无状态组件了,因为hooks为函数组件提供了状态
- hooks只能在函数组件中使用
2、 Hooks解决了什么问题
(1)组件的逻辑复用
在hooks出现之前,react先后尝试了 mixins混入,HOC高阶组件,render-props等模式,但是都有各自的问题,比如mixin的数据来源不清晰,高阶组件的嵌套问题等等
(2)class组件自身的问题
class组件就像一个厚重的‘战舰’ 一样,大而全,提供了很多东西,有不可忽视的学习成本,比如各种生命周期,this指向问题等等,而我们更多时候需要的是一个轻快灵活的'快艇'
二、useState——为函数组件提供状态(state)
1、使用案例
(1)做一个计数器,当点击按钮时,+1
(2)点击按钮时,名字由空为有值
// 导入
import { useState } from "react";
function App(){
// 解构:状态值 修改该状态的函数
const [num,setNum] = useState(0)
const [name,setName]=useState('')
const handleClick = (num)=>{
// 调用函数更改值
setNum(num+1)
}
return(
<>
<div>
{/* 渲染 */}
<span>{num}</span>
<button onClick={()=>handleClick(num)}>+</button>
</div>
<div>
{/* 渲染 */}
<span>{name}</span>
{/* 改变值 */}
<button onClick={()=>{setName('是一个开朗活泼聪明人见人爱的人')}}>点击看我是谁</button>
</div>
</>
)
}
export default App;
2、总结使用步骤
- 导入
useState
函数 - 调用
useState
函数,并传入状态的初始值,并从useState
函数的返回值中,解构赋值,拿到状态和修改状态的方法: const [num,setNum] = useState(0) - 在JSX中展示状态
- 调用修改状态的方法更新状态 setNum(num+1)
3、回调函数作为参数
(1)使用场景
如果初始 state 需要通过计算才能获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用
(2)语法
const [name, setName] = useState(()=>{
// 编写计算逻辑 return '计算之后的初始值'
})
4、组件的更新过程
(1)组件第一次渲染——从头开始执行该组件中的代码逻辑
- 调用
useState(0)
将传入的参数作为状态初始值,即:0 - 渲染组件,此时,获取到的状态 num值为: 0
(2)组件第二次渲染
- 点击按钮,调用函数修改状态,因为状态发生改变,所以,该组件会重新渲染
- 组件重新渲染:会再次执行该组件中的代码逻辑,再次调用
useState()
,此时 React 内部会拿到最新的状态值而非初始值,比如,该案例中最新的状态值为 1,因此再次渲染组件的组件获取到的状态 num值为:1
注意:useState 的初始值(参数)只会在组件第一次渲染时生效。也就是说,以后的每次渲染,useState 获取到都是最新的状态值,React 组件会记住每次最新的状态值
5、使用规则
(1)useState
函数可以执行多次,每次执行互相独立,每调用一次为函数组件提供一个状态
(2)react按照hooks的调用顺序识别每一个hook,因此不能嵌套在if/for/其它函数中,因为那个执行的顺序会变得不确定
(3) setNum(num+1) 传入的参数是新的值,会替换原来的值,一定要使用新的状态替换旧的状态,不能直接修改旧的状态(如num++),尤其是引用类型
(4)可以通过开发者工具查看hooks的状态
三、useEffect——为react函数组件提供副作用处理
1、函数副作用
(1)什么是函数副作用
对于 React 组件来说,主作用就是根据数据(state/props)渲染 UI,除此之外都是副作用
(2)常见的副作用
- 数据请求 ajax发送
- 手动修改dom
- localstorage操作
2、案例
在修改num数据之后,通过操作Dom,把num的值放到页面标题中
// 导入
import { useState,useEffect } from "react";
function App(){
const [num,setNum] = useState(0)
useEffect(()=>{
// 传递一个函数作为参数,在函数里面定义副作用
document.title=num
})
return(
<>
<button onClick={()=>{setNum(num+1)}}>+</button>
</>
)
}
export default App;
3、总结使用步骤:
- 导入
useEffect
函数 - 调用
useEffect
函数,并传入回调函数 - 在回调函数中编写副作用处理(dom操作)
- 修改数据状态(当我们通过修改状态更新组件时,副作用也会不断被执行)
4、依赖项控制执行时机
——似乎有点像vue中的监听,不确定再看看
(1)不添加依赖项
- 组件初始渲染
- 组件更新 (不管是哪个状态引起的更新)——我们刚刚的例子也是,如果有其他的状态,改变的话,同样也是会重新渲染的,但这种做法是不对的
useEffect(()=>{
console.log('副作用执行了')
})
(2) 添加空数组
组件只在首次渲染时执行一次
useEffect(()=>{
console.log('副作用执行了')
},[])
(3)添加特定依赖项
副作用函数在首次渲染时执行,在依赖项发生变化时重新执行(下面的例子中,只有count改变了才会执行,name改变时不会执行的)
function App() {
const [count, setCount] = useState(0)
const [name, setName] = useState('zs')
useEffect(() => {
console.log('副作用执行了')
}, [count])
return (
<>
<button onClick={() => { setCount(count + 1) }}>{count}</button>
<button onClick={() => { setName('cp') }}>{name}</button>
</>
)
}
(4)注意事项
useEffect 回调函数中用到的数据(比如,count)就是依赖数据,就应该出现在依赖项数组中,如果不添加依赖项可能就会有bug出现
5、使用useEffct hook发送网络请求
不可以直接在useEffect的回调函数外层直接包裹 await ,因为异步会导致清理函数无法立即返回
useEffect(async ()=>{
const res = await axios.get('http://geek.itheima.net/v1_0/channels')
console.log(res)
},[])
正确写法:
内部单独定义一个函数,然后把这个函数包装成同步
useEffect(()=>{
async function fetchData(){
const res = await axios.get('http://geek.itheima.net/v1_0/channels') console.log(res)
}
},[])
6、清除副作用
如果想要清理副作用 可以在副作用函数中的末尾return一个新的函数,在新的函数中编写清理副作用的逻辑
注意执行时机为:
- 组件卸载时自动执行——函数组件没有周期函数componentWillUnmount
- 组件更新时,下一个useEffect副作用函数执行之前自动执行
import { useEffect, useState } from "react";
function Test(){
useEffect(()=>{
let timer=setInterval(()=>{
console.log('定时器执行了')
},1000)
// 在这里清理
return()=>{
clearInterval(timer)
}
},[])
return(
<div>
happy
</div>
)
}
function App(){
const [show,setShow] = useState(true)
return(
<div>
{show ? <Test/> : null}
<button onClick={()=>{setShow(!show)}}>switch</button>
</div>
)
}
export default App;
四、阶段小练习
1、useWindowScroll
要求自定义一个hook函数,实现通过解构的形式获得滚动到顶部的距离y
(1)在hooks文件夹下创建一个useWindowScroll.js文件,实现并导出:
import { useState } from "react"
export function useWindowScroll(){
// y是一个数据,可以通过useState进行处理
const [y,setY] = useState(0)
//获取到数据后重新赋值
window.addEventListener('scroll',()=>{
const h = document.documentElement.scrollTop
setY(h)
})
return [y]
}
(2)App.js 导入并使用
import {useWindowScroll} from './hooks/useWindowScroll'
function App(){
const [y] = useWindowScroll()
console.log(y)
return(
<>
<div style={{height:'12000px'}}></div>
</>
)
}
export default App;
2、自动同步数据到本地localStorage
const [message, setMessage] = useLocalStorage(key,defaultValue)
- message可以通过自定义传入默认初始值
- 每次修改message数据的时候 都会自动往本地同步一份
import { useEffect, useState } from "react"
export function useLocalStorage(key,defaultVal){
const [message,setMessage] = useState(defaultVal)
useEffect(()=>{
window.localStorage.setItem(key,message)
},[message,key])
return [message,setMessage]
五、useRef——获取真实dom或组件实例
获取真实的dom:获取标签
获取组件实例:类组件(函数组件是没有实例)
1、示例
获取test组件实例和h1元素
import { Test} from "./Test";
// 导入 useRef 函数
import { useEffect, useRef } from "react";
function App(){
// 执行 useRef 函数并传入null,返回值为一个对象
const title = useRef(null)
const test =useRef(null)
// current属性存放dom对象(组件实例)
useEffect(()=>{
console.log(title.current,test.current)
},[])
return(
<div>
{/* 通过ref绑定要获取的对象 */}
<h1 ref={title}>我是标题</h1>
<Test ref={test}/>
</div>
)
}
export default App;
2、效果
3、总结使用步骤
(1)导入
(2)执行 useRef 函数并传入null const testRef = useRef(null)
(3)通过ref={testRef} 绑定
(4)通过testRef.current 获取
4、useRef 与 createRef
useRef
仅能用在 FunctionComponent,createRef
仅能用在 ClassComponent
import React,{Component,createRef} from "react";
class Test extends Component{
// 使用createRef函数,创建一个存放dom的对象容器
formRef=createRef()
// 通过this.formRef.current.value获取到表单元素的值
handleVal=()=>{
console.log(this.formRef.current.value)
}
render(){
return(
<>
{/* 通过ref绑定,获取真实dom */}
<input
ref={this.formRef}
onChange={this.handleVal}>
</input>
</>
);
}
}
六、useContext ——在hooks下的context使用方式
1、示例
// 导入
import React,{createContext, useContext}from "react";
// 创建对象实例
const Context = createContext()
function ComA(){
return(
<>
<div>this is SonA</div>
<ComC/>
</>
)
}
function ComC(){
// 通过useContext使用数据
const name = useContext(Context)
return(
<div>ComC接收到App的name是: {name}</div>
)
}
function App(){
const name='kk'
return (
// 通过Context.Provider标签提供数据
<Context.Provider value={name}>
<ComA></ComA>
</Context.Provider>
);
}
export default App;
2、对比之前的
其中Provider和Consumer都是从解构得到的:const { Provider, Consumer } = createContext()
// 通过Provider标签提供数据
<Provider value={name}>
<ComA></ComA>
</Provider>
// 通过Consumer获取数
<Consumer>
{value=><div>this is SonC,我得到来自App的数据是{value}</div>}
</Consumer>