React Hooks
React
使用 React Hooks 的无状态组件,在每次渲染时,包括组件内的函数,都会保留所有 state ,props 在执行时刻的值,这一点和 class 版本的组件具有很大的差异,class 版本会取到最新值。
import React, { Component } from 'react';
class OldApp extends Component {
constructor(props) {
super(props);
this.state = {
count: 1
}
}
testOnclick = () => {
setTimeout(() => {
console.log(this.state.count);
}, 3000);
}
render() {
return (
<div>
<div>
oldApp
</div>
<div>{this.state.count}</div>
<button onClick={() => { this.setState({ count: this.state.count+1 }) }} >add</button>
<button onClick={() => { this.testOnclick(); }} >show</button>
</div>
)
}
}
export default OldApp;
多次点击 add ,交错点击 show,console 里输出最新状态的 count n 次。
function App() {
const [count, setCount] = useState({ name: 1 });
function handleAlertClick() {
setTimeout(() => {
alert(count.name);
}, 3000);
}
return (
<div className="App">
<OldApp />
<p>{count.name}</p>
<button onClick={() => { setCount({ name: count.name + 1 }) }} > add</button>
<button onClick={() => { handleAlertClick() }} >alert</button>
</div>
);
}
export default App;
多次点击 add ,交错点击 alert ,console 多次出现被调用当时的值。
class 版本可以使用闭包修复,实际 Hooks 依赖 JavaScript 闭包。如果希望无状态组件获取到最新值,想 class 这样的表现,可以使用useRef
function App() {
const [count, setCount] = useState({ num: 1 });
const lastCount=useRef(count);
function handleAlertClick() {
lastCount.current = count;
setTimeout(() => {
console.log(lastCount.current.num);
}, 3000);
}
return (
<div className="App">
<p>{count.num}</p>
<button onClick={() => { setCount({ num: count.num + 1 }) }} > add</button>
<button onClick={() => { handleAlertClick() }} >alert</button>
</div>
);
}
React Hooks
-
useMemo:只有在某个依赖项改变时才会重新计算
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]); -
useCallback:把内联函数以及依赖数组作为参数传入,将返回回调函数的 memoized 版本。
const memoizedCallback = useCallback( () => { doSomething(a, b); }, [a, b], );直接定义的函数,在每次渲染时其实都会变化,这样的函数无法作为其他 Hook 的依赖存在,通过 useCallback 定义的函数,可作为其他 hooks 的依赖存在。当有多个 useEffect ,我们希望抽象出 useEffect 相同部分的逻辑,这部分逻辑依赖于 props 或者 state 时,可以考虑使用 useCallback。
-
useContext:上下文
import React from 'react'; export default React.createContext(null);import React, { useEffect, useState, useRef, useReducer, useContext } from 'react'; import './App.css'; import Test from './Test'; import ToDoContext from './ToDoContext'; function App() { const [count, dispatch] = useReducer(reducer, { num: 1 }) function reducer(state, action) { switch (action.type) { case 'add': return { ...state, num: state.num + 1 } } } return ( <ToDoContext.Provider value={{count,dispatch}} > <Test></Test> </ToDoContext.Provider> ); } export default App;import React, { useContext } from 'react'; import ToDoContext from './ToDoContext'; function Test() { const { count, dispatch } = useContext(ToDoContext); return ( <div>{count.num}</div> ) } export default Test;上下文用于解决 React 层层传递数据的问题,被包裹的子组件可以获取到全局数据,通常全局状态树会使用到上下文。
-
useEffect:在 render 之后执行的的方法,可以理解为
componentDidMount或者componentDidUpdate生命周期经常完成的操作,但是不一样的是,我们可以通过传递依赖的形式,确保代码仅在依赖变化时执行,这点我们之前使用shouldComponentUpdate进行props的对比类似。 -
useRef:使用 useRef 创建的对象和之间创建的对象的不同之处在于,useRef 返回的对象,在每次使用时会拿到最新值,而不是当次渲染值。
-
useReducer:当改变状态的逻辑很复杂时,我们通常使用 useReducer 来实现,而其他地方只需要 dispatch 相应的 type 不需要关心如何改变。同时结合useContext 我们可以做到统一的状态树管理
useEffect 详解
useEffect 用来处理会有副作用的操作,比如之前我们在生命周期函数中常常使用的获取数据操作。
useEffect(async () => {
const result = await axios(
'https://hn.algolia.com/api/v1/search?query=redux',
);
setData(result.data);
});
但是这样并不理想,因为不经在组件加载时会执行,在组件更新时也会执行,因此,对于只需要在加载阶段执行的操作,我们通常给予一个空依赖。
useEffect(async () => {
const result = await axios(
'https://hn.algolia.com/api/v1/search?query=redux',
);
setData(result.data);
}, []);
如此在第一次执行之后,useEffect 不会再执行,因为依赖未变化(为空)。
但是这样依然不完美,useEffect 并不希望函数有返回,而异步函数实际上会返回一个AsyncFunction ,会报警告,因此我们可以这样优化。
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'https://hn.algolia.com/api/v1/search?query=redux',
);
setData(result.data);
};
fetchData();
}, []);
以上操作实际使用 effects 模拟了传统的生命周期函数ComponentDidMount。而实际上我们应该用不同的眼光来看待useEffect。
例如常见的查询获取数据操作
const [search, setSearch] = useState('redux');
useEffect(() => {
const fetchData = async () => {
const result = await axios(
`http://hn.algolia.com/api/v1/search?query=${search}`,
);
setData(result.data);
};
fetchData();
}, [search]);
页面操作改变 search ,依赖于 search 的 effect 重新执行。
错误处理
const [isError, setIsError] = useState(false);
useEffect(() => {
const fetchData = async () => {
setIsError(false);
setIsLoading(true);
try {
const result = await axios(url);
setData(result.data);
} catch (error) {
setIsError(true);
}
setIsLoading(false);
};
fetchData();
}, [url]);
使用一个 state 存放错误,并显示在页面
自定义 Hook
我们将获取数据,错误判断,加载等从 APP 组件抽离,形成一个自定义的 Hooks。
const useHackerNewsApi = () => {
const [data, setData] = useState({ hits: [] });
const [url, setUrl] = useState(
'https://hn.algolia.com/api/v1/search?query=redux',
);
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
useEffect(() => {
const fetchData = async () => {
setIsError(false);
setIsLoading(true);
try {
const result = await axios(url);
setData(result.data);
} catch (error) {
setIsError(true);
}
setIsLoading(false);
};
fetchData();
}, [url]);
return [{ data, isLoading, isError }, setUrl];
}
在 App 中使用
function App() {
const [query, setQuery] = useState('redux');
const [{ data, isLoading, isError }, doFetch] = useHackerNewsApi();
return (
<Fragment>
<form onSubmit={event => {
doFetch(`http://hn.algolia.com/api/v1/search?query=${query}`);
event.preventDefault();
}}>
<input
type="text"
value={query}
onChange={event => setQuery(event.target.value)}
/>
<button type="submit">Search</button>
</form>
...
</Fragment>
);
}
同样可以抽离初始值
import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';
const useDataApi = (initialUrl, initialData) => {
const [data, setData] = useState(initialData);
const [url, setUrl] = useState(initialUrl);
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
useEffect(() => {
const fetchData = async () => {
setIsError(false);
setIsLoading(true);
try {
const result = await axios(url);
setData(result.data);
} catch (error) {
setIsError(true);
}
setIsLoading(false);
};
fetchData();
}, [url]);
return [{ data, isLoading, isError }, setUrl];
};
function App() {
const [query, setQuery] = useState('redux');
const [{ data, isLoading, isError }, doFetch] = useDataApi(
'https://hn.algolia.com/api/v1/search?query=redux',
{ hits: [] },
);
return (
<Fragment>
<form
onSubmit={event => {
doFetch(
`http://hn.algolia.com/api/v1/search?query=${query}`,
);
event.preventDefault();
}}
>
<input
type="text"
value={query}
onChange={event => setQuery(event.target.value)}
/>
<button type="submit">Search</button>
</form>
{isError && <div>Something went wrong ...</div>}
{isLoading ? (
<div>Loading ...</div>
) : (
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
)}
</Fragment>
);
}
export default App;

浙公网安备 33010602011771号