文章目录
Hooks概念理解
什么是Hooks
Hooks的本质:一套能够使函数组件更强大,更灵活的"钩子"
注意点:
- 有了hooks之后,为了兼容老版本,class类组件并没有被移除,俩者都可以使用
- 有了hooks之后,不能在把函数成为无状态组件了,因为hooks为函数组件提供了状态
- hooks只能在函数组件中使用
Hooks解决了什么问题
Hooks的出现解决了俩个问题 1. 组件的状态逻辑复用 2.class组件自身的问题
- 组件的逻辑复用:在hooks出现之前,react先后尝试了 mixins混入,HOC高阶组件,render-props等模式,但是都有各自的问题,比如mixin的数据来源不清晰,高阶组件的嵌套问题等等
- class组件自身的问题:class组件就像一个厚重的'战舰' 一样,大而全,提供了很多东西,有不可忽视的学习成本,比如各种生命周期,this指向问题等等,而我们更多时候需要的是一个轻快灵活的'快艇'
useState
基础使用
作用:
useState为函数组件提供状态(state)
使用步骤:
- 导入 useState 函数
- 调用 useState 函数,并传入状态的初始值
- 从useState函数的返回值中,拿到状态和修改状态的方法
- 在JSX中展示状态
- 调用修改状态的方法更新状态
代码实现:
javascript
import { useState } from 'react'
function App() {
// 参数:状态初始值比如,传入 0 表示该状态的初始值为 0
// 返回值:数组,包含两个值:1 状态值(state) 2 修改该状态的函数(setState)
const [count, setCount] = useState(0)
return (
<button onClick={() => { setCount(count + 1) }}>{count}</button>
)
}
export default App
状态的读取和修改
读取状态
该方式提供的状态,是函数内部的局部变量,可以在函数内的任意位置使用
修改状态
- setCount是一个函数,参数表示最新的状态值
- 调用该函数后,将使用新值替换旧值
- 修改状态后,由于状态发生变化,会引起视图变化
注意事项
- 修改状态的时候,一定要使用新的状态替换旧的状态,不能直接修改旧的状态,尤其是引用类型
组件的更新过程
函数组件使用 useState hook 后的执行过程,以及状态值的变化
- 组件第一次渲染
- 从头开始执行该组件中的代码逻辑
- 调用 useState(0) 将传入的参数作为状态初始值,即:0
- 渲染组件,此时,获取到的状态 count 值为: 0
- 组件第二次渲染
- 点击按钮,调用 setCount(count + 1) 修改状态,因为状态发生改变,所以,该组件会重新渲染
- 组件重新渲染时,会再次执行该组件中的代码逻辑
- 再次调用 useState(0),此时 React 内部会拿到最新的状态值而非初始值,比如,该案例中最新的状态值为 1
- 再次渲染组件,此时,获取到的状态 count 值为:1
重点一句话:useState 的初始值(参数)只会在组件第一次渲染时生效。也就是说,以后的每次渲染,useState 获取到都是最新的状态值,React 组件会记住每次最新的状态值
javascript
import { useState } from 'react'
function App() {
const [count, setCount] = useState(0)
// 在这里可以进行打印测试
console.log(count)
return (
<button onClick={() => { setCount(count + 1) }}>{count}</button>
)
}
export default App
使用规则
-
useState 函数可以执行多次,每次执行互相独立,每调用一次为函数组件提供一个状态 (也就是每个状态互不影响)
javascriptfunction List(){ // 以字符串为初始值 const [name, setName] = useState('cp') // 以数组为初始值 const [list,setList] = useState([]) }
-
useState 注意事项
-
只能出现在函数组件或者其他hook函数中
-
不能嵌套在if/for/其它函数中,只能在函数组件的最外层去调用(react按照hooks的调用顺序识别每一个hook)
javascriptlet num = 1 function List(){ num++ if(num / 2 === 0){ const [name, setName] = useState('cp') } const [list,setList] = useState([]) } // 俩个hook的顺序不是固定的,这是不可以的!!!
-
回调函数作为参数
使用场景
如果初始 state 需要通过计算才能获得,则可以传入一个函数。参数(也就是回调函数)只会在组件的初始渲染中起作用,后续渲染时会被忽略。
语法:
javascript
const [name, setName] = useState(()=>{
// 编写计算逻辑 return '计算之后的初始值'
})
语法规则
- 回调函数return出去的值将作为 name 的初始值
- 回调函数中的逻辑只会在组件初始化的时候执行一次
语法选择
-
如果就是初始化一个普通的数据 直接使用 useState(普通数据) 即可
-
如果要初始化的数据无法直接得到需要通过计算才能获取到,使用useState(()=>{})
例如我们来一个需求:
javascript
import { useState } from 'react'
function Counter(props) {
const [count, setCount] = useState(() => {
return props.count
})
return (
<div>
<button onClick={() => setCount(count + 1)}>{count}</button>
</div>
)
}
function App() {
return (
<>
<Counter count={10} />
<Counter count={20} />
</>
)
}
export default App
useEffect
什么是函数副作用
什么是副作用?
副作用是相对于主作用来说的,一个函数除了主作用,其他的作用就是副作用。对于 React 组件来说,主作用就是根据数据(state/props)渲染 UI,除此之外都是副作用(比如,手动修改 DOM)
常见的副作用
- 数据请求 ajax发送
- 手动修改dom
- localstorage操作
useEffect函数的作用就是为react函数组件提供副作用处理的!
你可以把useEffect看作是React生命周期方法的一种替代,useEffect可以模拟componentDidMount,componentDidUpdate,和componentWillUnmount的行为。你可以在一个组件中使用多个useEffect,以便将不同的副作用分开处理。useEffect是一个Hook,所以你只能在组件的顶层或者你自定义的Hook中调用它,不能在循环或条件语句中调用它。如果你不需要与外部系统同步,你可能不需要使用useEffect。
基础使用
作用
为react函数组件提供副作用处理
使用步骤
- 导入 useEffect 函数
- 调用 useEffect 函数,并传入回调函数
- 在回调函数中编写副作用处理(例如:dom操作)
- 修改数据状态
- 检测副作用是否生效
javascript
import { useEffect, useState } from 'react'
function App() {
const [count, setCount] = useState(0)
useEffect(()=>{
// dom操作
document.title = `当前已点击了${count}次`
})
return (
<button onClick={() => { setCount(count + 1) }}>{count}</button>
)
}
export default App
依赖项控制执行时机
1.不添加依赖项
组件首次渲染执行一次,以及不管是哪个状态更改引起组件更新时都会重新执行
- 组件初始渲染
- 组件更新 (不管是哪个状态引起的更新)
javascript
useEffect(()=>{
console.log('副作用执行了')
})
2.添加空数组
组件只在首次渲染时执行一次
javascript
useEffect(()=>{
console.log('副作用执行了')
},[])
3.添加特定依赖项
副作用函数在首次渲染时执行,在依赖项发生变化时重新执行
javascript
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>
</>
)
}
注意事项:
- useEffect 回调函数中用到的数据(比如,count)就是依赖数据,就应该出现在依赖项数组中,如果不添加依赖项就会有bug出现
- useEffect回调是在dom渲染之后执行
- useEffect和vue里的watch有点像,但是执行时机是不同的
清理副作用
如果想要清理副作用 可以在副作用函数中的末尾return一个新的函数,在新的函数中编写清理副作用的逻辑。
注意执行时机为:
- 组件卸载时自动执行
- 组件更新时,下一个useEffect副作用函数执行之前自动执行
javascript
import { useEffect, useState } from "react"
const App = () => {
const [count, setCount] = useState(0)
useEffect(() => {
const timerId = setInterval(() => {
setCount(count + 1)
}, 1000)
return () => {
// 用来清理副作用的事情
clearInterval(timerId)
}
}, [count])
return (
<div>
{count}
</div>
)
}
export default App
发送网络请求
使用场景
如何在useEffect中发送网络请求,并且封装同步 async await操作
语法要求
useEffect中不能是一个异步函数的原因是,useEffect的第一个参数应该是一个返回undefined或者一个清理函数的函数,而不是一个返回Promise的函数。异步函数会返回一个Promise,这个Promise不能被当作一个清理函数来调用。这样做会导致useEffect的行为不符合预期,可能会出现内存泄漏或者数据不一致的问题。
正确写法
如果你想在useEffect中使用异步函数,你有两种方法:
- 在useEffect内部定义一个异步函数,并立即调用它。
- 在useEffect外部定义一个异步函数,并在useEffect内部调用它。但是这样做的话,你需要把这个异步函数放到useEffect的依赖数组中,并且用useCallback来包裹它,以防止不必要的调用。
javascript
useEffect(() => {
// 定义一个异步函数
const fetchData = async () => {
// 你可以在这里使用await
const response = await MyAPI.getData(someId);
// ...
}
// 立即调用这个异步函数
fetchData();
}, [someId]); // 或者[]如果副作用不需要props或state
javascript
// 在useEffect外部定义一个异步函数,并用useCallback包裹它
const fetchData = useCallback(async () => {
// 你可以在这里使用await
const response = await MyAPI.getData(someId);
// ...
}, [someId]); // 这里的依赖项要和useEffect的一致
useEffect(() => {
// 在useEffect内部调用这个异步函数
fetchData();
}, [fetchData]); // 这里的依赖项要包含这个异步函数
useRef
使用场景
在函数组件中获取真实的dom元素对象或者是组件对象
使用步骤
- 导入 useRef 函数
- 执行 useRef 函数并传入null,返回值为一个对象内部有一个current属性存放拿到的dom对象(组件实例)
- 通过ref 绑定要获取的元素或者组件
代码实现:
获取dom
javascript
import { useEffect, useRef } from 'react'
function App() {
const h1Ref = useRef(null)
useEffect(() => {
console.log(h1Ref)
},[])
return (
<div>
<h1 ref={ h1Ref }>this is h1</h1>
</div>
)
}
export default App
获取组件实例
函数组件由于没有实例,不能使用ref获取,如果想获取组件实例,必须是类组件
javascript
class Foo extends React.Component {
sayHi = () => {
console.log('say hi')
}
render(){
return <div>Foo</div>
}
}
export default Foo
javascript
import { useEffect, useRef } from 'react'
import Foo from './Foo'
function App() {
const h1Foo = useRef(null)
useEffect(() => {
console.log(h1Foo)
}, [])
return (
<div> <Foo ref={ h1Foo } /></div>
)
}
export default App
UseContext
UseContext的作用是让你在组件中使用Context,Context是一种在组件树中传递数据的方式,它可以让你在不使用props的情况下,让深层嵌套的组件访问一些全局的状态。使用UseContext的步骤如下:
- 首先,你需要使用createContext创建一个Context对象,并给它一个默认值。
- 然后,你需要使用Context.Provider组件包裹你的组件树,并给它一个value属性,这个value就是你要传递的数据。
- 最后,你可以在任何子组件中使用useContext(Context)来获取这个value,这样你就可以在组件中使用这个数据了。
使用UseContext的好处是,你不需要通过props来一层一层地传递数据,这样可以避免一些不必要的渲染和复杂的逻辑。你也可以在Context中存储一些函数,以便在组件中调用它们。使用UseContext的注意事项是,你需要保证你使用的Context对象是同一个,否则你可能会得到undefined的值。你也需要注意useContext的依赖关系,如果你的Context的value发生变化,那么所有使用了这个Context的组件都会重新渲染,除非你使用了memo或者useMemo来优化性能。
代码实现:
javascript
import { createContext, useContext } from 'react'
// 1.创建Context对象
const Context = createContext()
function Foo() {
return <div>Foo <Bar/></div>
}
function Bar() {
// 3.底层组件通过useContext函数获取数据
const name = useContext(Context)
return <div>Bar {name}</div>
}
function App() {
return (
// 2.顶层组件通过Provider 提供数据
<Context.Provider value={'this is name'}>
<div><Foo/></div>
</Context.Provider>
)
}
export default App