零:hook解决的问题:
避免地狱式嵌套,内部无法判断props是来源,可读性高;
class组件生命周期太多太复杂,使函数组件存在状态,带组件状态的逻辑很难重用;
ui和逻辑更容易分离.
一、hooks的使用
1、useState:
const [state,setState] = useState(initialState);
setState函数用于更新state,接受一个新的state的值,并将组件的一次重新渲染加入队列.
传统的Class Component:
class Example extends React.Component{
constructor(props){
super(props);
this.state = {
count: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(){
const [count,setCount] = useState(0)
return(
<div>
<p>You clicked {count} times</p>
<button onClick={()=>setCount(count+1)}>Click me</button>
</div>
)
}
useState方法的入参只有一个就是初始值,这个初始值可以是一个数字,字符串或对象,甚至可以是一个函数,当入参是函数时,函数只会在这个组件初始渲染的时候执行.
useState的返回值是一个数组,第一项是state当前值,第二项是改变state的方法.
更新state的方式:
setCount(count+1)
或者
setCount(preCount => {
return preCount+1
})
两者的区别:
function Counter() {
const [count, setCount] = useState(0);
//快速点击的时候只+1次1
function handleClick() {
setTimeout(() => {
setCount(count + 1)
}, 3000);
}
// 快速点击的时候每次都会+1
function handleClickFn() {
setTimeout(() => {
setCount((prevCount) => {
return prevCount + 1
})
}, 3000);
}
return (
<>
Count: {count}
<button onClick={handleClick}>+</button>
<button onClick={handleClickFn}>+</button>
</>
);
}
注意:如果state是一个对象,setState的时候不会向Class Component的setState那样自动合并对象,如果需要合并,需要:
setState(prevState => {
return {...prevState,...updatedValues};
})
setState的参数除了数字,字符串或对象,还可以是函数.与Class Component的setState很像的一点是,当传入的值跟之前的值一样时(使用Object.is比较),不会触发更新.
2、useEffect:
会在每次DOM渲染后执行,不会阻塞页面渲染,具备
componentDidMount,
componentDidUpdate,
componentWillUnmount三个生命周期函数的执行时机
useEffect实现componentDidMount:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function App() {
const [data, setData] = useState({ hits: [] });
const [query, setQuery] = useState('redux');
const [search, setSearch] = useState('');
useEffect(() => {
const fetchData = async () => {
const result = await axios(
`http://localhost/api/v1/search?query=${query}`,
);
setData(result.data);
};
fetchData();
}, [search]);
return (
<Fragment>
<input
type="text"
value={query}
onChange={event => setQuery(event.target.value)}
/>
<button type="button" onClick={() => setSearch(query)}>
Search
</button>
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
</Fragment>
);
}
export default App;
useEffect实现componentDidUpdate:(先执行一次,当count改变的时候再执行)
//仅当count改变的时候才执行Effect
useEffect(async () => {
...
},[count]);
useState修改数据会触发页面重新渲染,页面重新渲染后,会根据情况执行useEffect
应用:
自定义hooks,引用封装好的hooks方便复用解耦.
const useHackerNewsApi = () => {
...
useEffect(
...
);
const doFetch = url => {
setUrl(url);
};
return { data, isLoading, isError, doFetch };
};
function App() {
const [query, setQuery] = useState('redux');
const { data, isLoading, isError, doFetch } = useHackerNewsApi();
return (
<Fragment>
<form
onSubmit={event => {
doFetch(
`http://localhost/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>
);
}
跟 useState
一样,你可以在组件中多次使用 useEffect.
通过使用 Hook,你可以把组件内相关的副作用组织在一起(例如创建订阅及取消订阅),而不要把它们拆分到不同的生命周期函数里:
function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
}
3、useLayoutEffect:
用法跟useEffect用法是一样的,唯一的区别是执行的时机.
useLayoutEffect会阻塞页面渲染,如果在其中执行耗时的任务的话,会卡顿.绝大多数下,useEffect是更好的选择,除非:需要根据新的ui进行DOM操作,useLayoutEffect会保证在页面渲染前执行,而useEffect会因为渲染了两次而出现抖动
4、useContext:
如果想让Navbar组件和Messages组件共享数据,就会用到context.
const TestContext = React.createContext({});
<TestContext.Provider
value={{
username: 'superawesome',
}}
>
<div className="test">
<Navbar />
<Messages />
</div>
<TestContext.Provider/>
//navbar组件使用公共数据如下
const Navbar = () => {
const { username } = useContext(TestContext);
return (
<div className="navbar">
<p>{username}</p>
</div>
)
}
5、useReducer:
useReducer
的用法跟 Redux 非常相似,当 state 的计算逻辑比较复杂又或者需要根据以前的值来计算时,使用这个 Hook 比useState
会更好
function init(initialCount) {
return {count: initialCount};
}
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
case 'reset':
return init(action.payload);
default:
throw new Error();
}
}
function Counter({initialCount}) {
// 第一个参数是reducer,用dispatch触发
// 第二个参数是初始state的值
// 第三个参数是惰性初始化state,这样初始 state 将被设置为 init(initialCount)
const [state, dispatch] = useReducer(reducer, initialCount, init);
return (
<>
Count: {state.count}
<button
onClick={() => dispatch({type: 'reset', payload: initialCount})}>
Reset
</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
);
}
6、useCallback/useMemo/React.memo:可以在依赖不变的情况下,避免不必要的重复渲染
useMemo:缓存变量
import React from 'react';
export default function WithoutMemo() {
const [count, setCount] = useState(1);
const [val, setValue] = useState('');
// 每次都需要进行重复计算
function expensive() {
console.log('compute');
let sum = 0;
for (let i = 0; i < count * 100; i++) {
sum += i;
}
return sum;
}
return <div>
<h4>{count}-{val}-{expensive()}</h4>
<div>
<button onClick={() => setCount(count + 1)}>+c1</button>
<input value={val} onChange={event => setValue(event.target.value)}/>
</div>
</div>;
}
可以改成:只有count改变的时候才会重新计算,否则返回上一次计算的值
const expensive = useMemo(() => {
console.log('compute');
let sum = 0;
for (let i = 0; i < count * 100; i++) {
sum += i;
}
return sum;
}, [count]);
useCallback缓存函数:const fnA = useCallback(fnB, [a]),当a变化时返回fnB
import React, { useState, useCallback } from 'react';
export default function Callback() {
const [count, setCount] = useState(1);
const [val, setVal] = useState('');
const callback = useCallback(() => {
console.log(count);
}, [count]);
return <div>
<h4>{count}</h4>
<div>
<button onClick={() => setCount(count + 1)}>+</button>
<input value={val} onChange={event => setVal(event.target.value)}/>
</div>
</div>;
}
7、React.memo:相当于shouldComponentUpdate方法,区别是它只能比较props,不会比较state
const Parent = React.memo(({ a, b }) => {
// 当 a 改变时才会重新渲染
const child1 = useMemo(() => <Child1 a={a} />, [a]);
// 当 b 改变时才会重新渲染
const child2 = useMemo(() => <Child2 b={b} />, [b]);
return (
<>
{child1}
{child2}
</>
)
});
唯一的区别是:useCallback 不会执行第一个参数函数,而是将它返回给你,而 useMemo 会执行第一个函数并且将函数执行结果返回给你。所以 useCallback 常用记忆事件函数,生成记忆后的事件函数并传递给子组件使用。而 useMemo 更适合经过函数计算得到一个确定的值,比如记忆组件。
8、useRef:
之前的Class Component获取ref的方式如下:
class MyComponent extends React.Component{
constructor(props){
super(props);
this.myRef = React.createRef();
}
componentDidMount(){
this.myRef.current.focus();
}
render(){
return <input ref={this.myRef} type='text'/>
}
}
hooks的实现方式如下:
function MyComponent(){
const myRef = useRef();
useEffect(()=>{
myRef.current.focus();
})
return <input ref={myRef} type='text'/>
}
useRef返回的是一个普通的js对象,可以将任意数据存到current属性里,
useRef可以跨越渲染周期存储数据,而且对他的修改也不会引起组件渲染.