飞雪连天射白鹿,笑书神侠倚碧鸳

0%

react-useEffect&useReducer

use*** 格式定义的函数是react hooks的规范

useEffect用于处理组件中的effect,通常用于请求数据,事件处理,订阅等相关操作

最后return的【返回函数】可以作为状态切换的默认执行函数,执行下一个effect之前执行上一个effect【返回的函数】


  • 状态和状态更新函数来自与useState这个hooks,通过调用useState,来创建App组件的内部状态。初始状态是一个object,其中的hits为一个空数组

  • axios发起请求,使用useEffect来隔离副作用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    import React, { useState } from 'react';
    import axios from 'axios';

    function App() {
    const [data, setData] = useState({ hits: [] });

    useEffect(()=>{
    const fetchData = async(){
    const result = await axios('xxx');
    setData(result.data);
    }
    fetchData()
    },[])
    return (
    <ul>
    {data.hits.map(item => (
    <li key={item.objectID}>
    <a href={item.url}>{item.title}</a>
    </li>
    ))}
    </ul>
    );
    }

    export default App;

    useEffect在组件mount时执行,但也会在组件更新时执行

  • 如果在useEffect中请求数据并setData会触发渲染,导致死循环
    所以需要传递一个空数组作为参数,避免在更新时触发useEffect,只会在mount挂载时执行一次

  • 第二参数用于监听指定变量的更新
    如果参数中定义的变量更新了,则useEffect会再次执行

  • async函数会隐式返回promise,但是useEffect不返回内容,所以需要用方法包装async


响应更新的例子,用户输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function App() {
const [data, setData] = useState({ hits: [] });
const [query, setQuery] = useState('redux');
const [search, setSearch] = useState('');
const [url, setUrl] = useState('http://localhost/api/v1/search?query=redux');

useEffect(()=>{
const fetchData = async()=>{
const result = await axios('xxx?query=' + query);
setData(result.data);
}
fetchData()
},[search])
return (
<Fragment>
<input type="text" value={query}
onChange={event => setQuery(event.target.value)}
/>
<ul>
<button type="button" onClick={() => setSearch(query)}>
Search
</button>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
</Fragment>
);
}
  • query的任何一次变动都会请求后端,这样会带来比较大的访问压力。这个时候我们需要引入一个按钮,点击这个按钮再发起请求(query改为search)

  • 每次点击按钮时,会把search的值设置为query,这个时候我们需要修改useEffect中的依赖项为search,这样每次点击按钮,search值变更,useEffect就会重新执行,避免不必要的变更

  • 因为组件首先会在mount时获取数据。所以简单点,直接将的要请求的后端URL设置为search state的初始值

  • 如果useEffect中出现其他外部变量也需要放入依赖数组中,所以btn依赖的query也提取到btn中,不转入useEffect

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    function App() {
    const [data, setData] = useState({ hits: [] });
    const [query, setQuery] = useState('redux');
    const [url, setUrl] = useState(
    'http://localhost/api/v1/search?query=redux',
    );

    useEffect(() => {
    const fetchData = async () => {
    const result = await axios(url);

    setData(result.data);
    };

    fetchData();
    }, [url]);

    return (
    <Fragment>
    <input
    type="text"
    value={query}
    onChange={event => setQuery(event.target.value)}
    />
    <button type="button" onClick={() => setUrl(`http://localhost/api/v1/search?query=${query}`)}>
    Search
    </button>
    <ul>
    {data.hits.map(item => (
    <li key={item.objectID}>
    <a href={item.url}>{item.title}</a>
    </li>
    ))}
    </ul>
    </Fragment>
    );
    }

    处理Loading和error

  • 在请求后端数据,展现loading的状态

  • 不需要再loading变更时重新调用useEffect

  • 处理错误,使用useState来创建一个新的state,然后在useEffect中特定的位置来更新这个state

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    function App() {
    const [isLoading, setIsLoading] = useState(false);
    useEffect(() => {
    const fetchData = async () => {
    setIsLoading(true);

    const result = await axios(url);

    setData(result.data);
    setIsLoading(false);
    };

    fetchData();
    }
    return (
    <Fragment>
    {isLoading ? (
    <div>Loading ...</div>
    ) : (
    <ul>
    {data.hits.map(item => (
    <li key={item.objectID}>
    <a href={item.url}>{item.title}</a>
    </li>
    ))}
    </ul>
    )}
    </Fragment>
    )
    }

    同名目录内有抽离自定义hooks组件的例子
    使用useReducer整合逻辑

  • 使用了各种通过分离的state hooks来管理分散的:数据、状态,为了有关联的状态整合到一起,我们需要用到useReducer

  • useReducer 是一个轻量的redux,返回一个状态对象和一个可以改变状态对象的dispatch函数,dispatch函数接受action作为参数,action包含type和payload属性

  • useReducer将reducer函数和初始状态对象作为参数,即同时定义多个hooks

  • type属性告诉reducer需要应用哪个状态转换,并且reducer可以使用payload来创建新的状态

  • 例子中的state是对象,导出时可以解构分离原有状态

  • 取消数据请求,在请求还没有返回的时候卸载了组件会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const dataFetchReducer = (state, action) => {
switch (action.type) {
case 'FETCH_INIT':
return {
...state,
isLoading: true,
isError: false
};
}
};
const [state, dispatch] = useReducer(dataFetchReducer, {
isLoading: false,
isError: false,
data: initialData,
});

dispatch({ type: 'FETCH_INIT' });
dispatch({ type: 'FETCH_SUCCESS', payload: result.data });
1
2
3
4
5
6
在没有hooks时,创建class组件,性能的优化通过immutable比较,
1、在不相等的时候调用setState,
2、在shouldComponentUpdate中判断前后的props和state,如果没有变化,则返回false来阻止更新

在hooks出来后,无法通过判断前后状态来决定是否更新,每一次调用都会执行其内部的所有逻辑,可以发现每次改变都会触发父子组件渲染
性能损耗通过useMemo 和useCallback解决
听说,打赏我的人最后都找到了真爱
↘ 此处应有打赏 ↙
// 用户脚本