Hooks 概念理解
学习目标: 理解 Hooks 的概念及解决的问题
- 什么是 hooks
hooks 的本质: 一套能够使函数组件更强大、更灵活的(钩子)
React 体系里组件分为类组件和函数组件
多年使用发现,函数组件是一个更加匹配 React 的设计理念UI = f(date)
,也更加有利于逻辑拆分与重用的组件表达形式,而先前组件是不可以有自己的状态的。为了让函数组件可以拥有自己的状态,所以从 react v16.8 开始,加入了 hooks
注意点:
- 有了 Hooks 之后,为了兼容老版本,class 类组件并没有被移除,两者都可以使用
- 有了 Hooks 之后,不能在把函数成为无状态组件了,因为 Hooks 为函数组件提供了状态
- Hooks 只能在函数组件中使用
- Hooks 解决了什么问题
Hooks 的出现解决了两个问题 1.组件的逻辑复用 - 组件逻辑复用
在 Hooks 出现之前,react 先后尝试了 mixin 混入、HOC 高阶组件、render-props 等模式但是都有各自的问题,比如 mixin 的数据来源不清、高阶组件的嵌套问题等 - class 组件自身的问题
class 组件过于厚重,大而全,提供了很多东西,有很高的学习成本,比如繁多的生命周期、this 指向问题等,而更多时候需要轻快的使用组件 - Hooks 优势
- 告别那一理解的 class
- 解决业务逻辑难以拆分的问题
- 使状态逻辑复用变的简单
- 函数组件在设计思想上与 react 的理念契合
useState
- 基础使用
学习目标: 能够学会 useState 的基础用法
作用: useState 为函数组件提供状态(state)
使用步骤: - 导入
useState
函数 - 调用
useState
函数,并传入状态初始值 - 从
useState
函数的返回值中,拿到状态和修改状态的方法 - 在 JSX 中展示状态
- 调用修改状态的方法更新状态
代码实现:
import { useState } from 'react'
function App() {
const [count, setCount] = useState(0)
return (
<div>
<button onClick={() => setCount(count + 1)}>{count}</button>
</div>
)
}
export default App
状态读取和修改
const [count, setCount] = useState(0) // 解构赋值 useState 返回值是一个数组
- useState 传过来的参数 作为 count 的初始值
- [count, setCount] 名字可以自定义保持语义化顺序不可以变 第一个参数是数据状态 第二个参数是修改数据的方法
- setCount 函数用来修改 count 依旧保持不能直接修改原值还是生成一个新值替换原值 setCount(基于运算得到的新值)
- count 和 setCount 是一对 是绑在一起的 setCount 只能用来修改对应的 count 值
组件的更新过程
学习目标: 能够理解使用 Hooks 之后组件的更新状态
函数组件使用 useState hook 后的执行过程,以及状态值的变化
- 组件第一次渲染
- 从头开始执行该组件中的代码逻辑
- 调用
useState(0)
将传入参数作为状态初始值,即:0 - 渲染组件,此时,获取到的状态 count 值为:0
- 组件第二次渲染 1. 点击按钮,调用
setCount(count +1)
修改状态,因为状态发生改变,所以该组件会重新渲染 2. 组件重新渲染时,会再次执行组件中的代码逻辑 3. 再次调用setCount(0)
,此时 React 内部会拿到最新的状态值而非初始值 4. 再次渲染组件时获取到的状态 count 值为:1
注意:useState 的初始值只有在组件第一次渲染时生效,以后的每一次渲染,useState 获取到的都是最新的状态值
使用规则
学习目标: 能够记住 useState 的使用规则
-
useState
函数可以执行多次,每次执行互相独立,每调用一次为函数组件提供一个状态
function List() {
// 字符串状态值
const [name, setName] = useState('cp')
// 数组状态值
const [list, setList] = useState([])
}
- useState 注意事项
- 只能出现在函数组件中
- 不能嵌套在 if、for、其他函数中(react 按照 hooks 的调用顺序识别每一个 hook)
let num = 1
function List() {
num++
if (num / 2 === 0) {
const [name, setName] = useState('cp')
}
const [list, setList] = useState([])
}
// 两个hook的顺序不是固定的,这是不可以的
- 可以通过开发者工具查看 hooks 状态
代码示例:
import { useState } from 'react'
function App() {
const [count, setCount] = useState(0)
const [show, setShow] = useState(false)
const [list, setList] = useState([])
function test() {
setShow(!show)
setList([1, 2, 3, 4])
console.log(show, list)
}
return (
<div>
<button onClick={() => setCount(count + 1)}>count:{count}</button>
<button onClick={test}>
show:{show ? 'true' : 'false'}---list:{list}
</button>
</div>
)
}
export default App
useEffect 函数
基础使用
- 理解函数副作用
学习目标: 能够理解函数副作用的概念
什么是函数副作用:
副作用是相对于主作用来说的,一个函数除了主作用,其他的作用就是副作用,对于 React 组件来说主作用是根据数据(state\props)渲染 UI,除此之外都是副作用(比如,手动修改 DOM)
常见副作用: - 数据请求 ajax
- 手动修改 DOM
- localstorage 操作
useEffect 函数的作用就是为 React 函数组件提供副作用处理
- 基础使用
学习目标: 能够学会 useEffect 的基础用法并且掌握默认的执行时机
作用 为 React 函数组件提供副作用处理
使用步骤: - 导入
useEffect
函数 - 调用
useEffect
函数,并传入回调函数 - 在回调函数中编写副作用处理(DOM 操作)
import { useState, useEffect } from 'react'
function App() {
const [count, setCount] = useState(0)
useEffect(() => {
document.title = count
})
return (
<div>
<button onClick={() => setCount(count + 1)}>count:{count}</button>
</div>
)
}
export default App
依赖项控制执行时机
- 默认状态(无依赖项) 组件初始化时先执行一次 等每次数据修改更新再次执行
useEffect(() => {
console.log('副作用执行了')
})
- 添加空数组依赖项 组件只在首次渲染时执行一次 其余时间不执行
useEffect(() => {
console.log('副作用执行了')
}, [])
- 添加特定依赖项 副作用函数在首次渲染时执行,在依赖项发生变化时再次执行
import { useState, useEffect } from 'react'
function App() {
const [count, setCount] = useState(0)
const [name, setName] = useState('cp')
useEffect(() => {
console.log('副作用执行了')
document.title = count
}, [count, name])
return (
<div>
<button onClick={() => setCount(count + 1)}>count:{count}</button>
<button onClick={() => setName('hs')}>name:{name}</button>
</div>
)
}
export default App
注意事项: 只要在 useEffect 回调函数中用到的数据(比如,count)就是依赖数据,就应该出现在依赖项数组中,如果不添加依赖项就会有 bug 出现
useState - 回调函数的参数
学习任务: 能够理解 useState 回调函数作为参数的使用场景
使用场景:
参数只会在组建的初始值渲染中起作用,后续渲染时会被忽略,若果初始 State 需要通过计算才能获得,则可以使用一个回调函数,在函数中计算并返回 State,此函数只在初始渲染中起作用
语法
const [name,setName] = useState(()=>{ //编写组算逻辑 return "计算之后的结果"})
语法规则:
- 回调函数 return 出去的值将作为 name 的初始值
- 回调函数中的逻辑只会在组建初始化的时候执行一次
语法选择: - 如果就是初始化一个普通的数据,直接使用
useState(普通数据)
即可 - 如果要初始化的数据无法直接得到需要通过计算才能获取到,使用
useState(()=>{})
代码实现:
import { useState } from 'react'
function getDefaultValue() {
for (let i = 1; i < 10000; i++) {}
return '10'
}
function Counter(props) {
const [count, setCount] = useState(() => {
return getDefaultValue()
})
return <button onClick={() => setCount(count + 1)}>{count}</button>
}
function App() {
return (
<div>
<Counter count={10} />
<Counter count={20} />
</div>
)
}
export default App
useEffect - 清理副作用
学习目标: 能够掌握清理 useEffect 的方法
使用场景:
在组件被销毁时,如果有些副作用需要被清理,就可以使用此语法,比如常见的定时器
语法及规则
useEffect(() => {
console.log('副作用函数被执行')
//副作用函数的执行时机为:在下一次副作用函数执行之前
return () => {
console.log('清理副作用的函数被执行了')
// 在这里写清理副作用的代码
}
})
定时器案例代码实现:
import { useState, useEffect } from 'react'
function Test() {
useEffect(() => {
let time1 = setInterval(() => {
console.log('定时器执行了')
}, 1000)
return () => {
//清理定时器
clearInterval(time1)
}
}, [])
return <div>this is Test</div>
}
function App() {
const [flag, setFlag] = useState(true)
return (
<div>
{flag ? <Test /> : null}
<button onClick={() => setFlag(!flag)}>switch</button>
</div>
)
}
export default App
useEffect - 发送网络请求
学习目标: 能够掌握使用 useEffect hook 发送网络请求
使用场景: 如何在 useEffect 中发送网络请求,并且封装同步 async await 操作
语法要求: 不可以直接在 useEffect 的回调函数外层直接包裹 await,因为异步会导致清理函数无法立即返回
useEffect(async () => {
const res = await axios.get('http://geek.itheima.net/v1_0/channels')
console.log(res)
}, [])
正确写法:
useEffect(()=>{
async function fetchDate(){
const res = await axios.get('http://geek.itheima.net/v1_0/channels)
console.log(res)
}
},[])
useRef
学习目标: 能够掌握 useREf 获取真实 DOM 或组件实例的方法
使用场景: 在函数组件中获取真实的 DOM 元素对象或者组件对象
使用步骤
- 导入
useRef
函数 - 执行
useRef
函数并传入 null,返回值为一个对象,内部有一个 current 属性存放拿到的 DOM 对象(组件实例) - 通过 ref 绑定要获取的元素或者组件
代码示例
import React, { useState, useEffect, useRef } from 'react'
class TestC extends React.Component {
state = { name: 'testName' }
getName = () => {
return 'this is child Test'
}
render() {
return <div>我是类组件</div>
}
}
function App() {
const TestRef = useRef(null)
const h1Ref = useRef(null)
useEffect(() => {
console.log(TestRef.current)
console.log(h1Ref.current)
})
return (
<div>
<TestC ref={TestRef}></TestC>
<h1 ref={h1Ref}>This is h1</h1>
</div>
)
}
export default App
useContext
学习目标: 能够掌握hooks下的Context使用方法
实现步骤:
- 使用
createContext
创建Context对象 - 在顶层组件通过
Provider
提供数据 - 在底层组件通过
useContext
函数获取数据
代码实现:
import React, { createContext, useState, useContext } from "react"
const Context = createContext()
function ComA () {
const count = useContext(Context)
return (
<div>
this is ComA
App传过来的数据{count}
<ComC />
</div>
)
}
function ComC () {
const count = useContext(Context)
return (
<div>
this is ComC
App传过来的数据{count}
</div>
)
}
function App () {
const [count, setCount] = useState(20)
return (
<Context.Provider value={count}>
<div>
<ComA />
<button onClick={() => { setCount(count + 1) }}>+</button>
</div>
</Context.Provider>
)
}
export default App