Hook是什么
Hook 是一个特殊的函数,它可以让你“钩入” React 的特性。例如,useState 是允许你在 React 函数组件中添加 state 的 Hook。
什么时候使用Hook
编写函数组件并需要向其添加一些 state
常用hook
useState
语法
const [count,setCount]=useState(0)
解释
通过useState()声明,会返回一个有两个元素的数组,即当前的状态和更新函数。useState的参数是初始化参数。在必要时,可使用其对应更新函数设置count的值。
对比class
传统class写法
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0//初值设为0
};
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
import React, { useState } from 'react';
function Example() {
// 声明一个叫 "count" 的 state 变量
const [count, setCount] = useState(0)
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
异同
- count值展示:class中 {this.state.count};hook中:{count}
- count值更新:class中 this.setState({ count: this.state.count + 1 };hook中:setCount(count + 1)
注意一点:Hook 在 class 内部是不起作用的
补充
const [count, setCount] = useState(0)
相当于 es6 中的数组解构,所以这句代码等同如下代码
const _useState = useState(0)
const count = _useState[0]
const setCount = _useState[1]
- 根据
useState
出现的顺序来保证这useState找到它自己对应的state,故useState不能存在于条件语句中
useEffect
Effect Hook 可以让你在函数组件中执行副作用操作,即在dom更新后还有其他额外操作。
这个例子,是我们通过data的初始值进行创建列表,但是当这个列表内容需要从网络请求获取时,我们就得使用到useEffect,来隔离副作用
import React, { useState } from 'react';
export default function App(){
const [data, setData] = useState({ hits: [{id:1,typename:'a'},{id:2,typename:'b'}] });
return (
<ul>
{data.hits.map(item => (
<li key={item.id}>
{item.typename}
</li>
))}
</ul>
);
}
添加网络请求
import React, { useState } from 'react';
import axios from 'axios';
export default function App(){
const [data, setData] = useState({ hits: [{id:1,typename:'a'},{id:2,typename:'b'}] });
useEffect(async () => {
const fetchData = async () => {
const result = await axios(
'http://localhost/api/v1/search?query=redux',
);
setData(result.data);
};
fetchData();
});
return (
<ul>
{data.hits.map(item => (
<li key={item.id}>
{item.typename}
</li>
))}
</ul>
);
}
在useEffect中,不仅会请求后端的数据,还会通过调用setData来更新本地的状态,这样会触发view的更新。但是在运行这个程序时会触发无限循环。
原因:useEffect在组件mount时执行,也会在组件更新时执行。因为我们在每次请求数据之后都会设置本地的状态,所以组件会更新,因此useEffect会再次执行,因此出现了无限循环的情况。
解决: 我们只想在组件mount时请求数据。我们可以传递一个空数组作为useEffect的第二个参数,这样就能避免在组件更新执行useEffect,只会在组件mount时执行。
修改如下,只会在组件mount时执行一次
import React, { useState } from 'react';
import axios from 'axios';
export default function App(){
const [data, setData] = useState({ hits: [{id:1,typename:'a'},{id:2,typename:'b'}] });
useEffect(async () => {
const fetchData = async () => {
const result = await axios(
'http://localhost/api/v1/search?query=redux',
);
setData(result.data);
};
fetchData();
},[]);
return (
<ul>
{data.hits.map(item => (
<li key={item.id}>
{item.typename}
</li>
))}
</ul>
);
}
然而在实际开发中,我们请求可能会依赖于某个变量,这时useEffect第二个参数便可填写上这个变量。mount时执行,并且如果变量发生变化,useEffect会再次运行。
useEffect怎么实现 componentWillUnmount
在组件卸载时,我们常常要做一些操作。例如,清除定时器,避免内存泄漏;或是登录状态的取消。
useEffect 提供了 return 方法以供我们在组件卸载时清空副作用,举例如下
useEffect(()=>{
console.log(`You clicked ${count} times`)
return ()=>{
console.log('组件销毁')
}
},[])
第二个参数为空,则相当于只执行一次。若不加,则会在每次渲染时都执行。
useContext 父子组件传值
用类声明组件时,父子组件的传值是通过组件属性和props进行的。用了方法来声明组件,就没有了constructor
,当然也就没有了props
。useContext
和 redux
的作用是不同的,一个解决的是组件之间值传递的问题,一个是应用中统一管理状态的问题,但通过和useReducer
的配合使用,可以实现类似 Redux
的作用。
Context的作用就是对它所包含的组件树提供全局共享数据的一种技术。
步骤为:
- createContext 函数创建context
- useContext 接收上下文变量
父组件
import React, { useState , createContext } from 'react'
import Son from './Son'
const CountContext = createContext()
function Father(){
const [ count , setCount ] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={()=>{setCount(count+1)}}>click me</button>
<CountContext.Provider value={count}>
<Son />
</CountContext.Provider>
</div>
)
}
export default Father
子组件
import React, { useContext } from 'react'
function Son(){
const count = useContext(CountContext)
return (<h2>{count}</h2>)
}
export default Son
useReducer 基础应用
Reducer?形如下面这段代码就可被称为reducer
function countReducer(state, action) {
switch(action.type) {
case 'add':
return state + 1;
case 'dec':
return state - 1;
default:
return state;
}
}
useReducer 使用 demo, 计数器加减
function ReducerDemo(){
const [ count , dispatch ] =useReducer((state,action)=>{
switch(action){
case 'add':
return state+1
case 'dec':
return state-1
default:
return state
}
},0)
return (
<div>
<h2>分数{count}</h2>
<button onClick={()=>dispatch('add')}>Increase</button>
<button onClick={()=>dispatch('dec')}>Decrease</button>
</div>
)
}
问题及解决
在实际开发中,我们常常会遇到,可能组建中发送了一个请求,然而请求还未返回就卸载了组件,这时候,还设置这个状态,是会报错的
错误如下
解决方法:在这里只给出了部分代码
useEffect(() => {
let didCancle = false
setLoading(true)
getWiki({ page, status: 4, isAdmin: false }).then(
({ dataList, msg, pageInfo }) => {
let list: IArticle[]
if (data && data.dataList) {
list = [...data.dataList, ...dataList]
} else {
list = dataList
}
if (!didCancle) {
setData({ dataList: list, pageInfo, msg })
setLoading(false)
}
},
err => {
setLoading(false)
toast(err)
}
)
return () => {
didCancle = true
}
}, [page])
新增 didCancle 变量,如果变量为true则不再去设置组件渲染所以依赖的数据。