react hooks 学习
一. React Hooks是什么?
React Hooks 是 React 16.8 引入的新特性,允许我们在不使用 Class 的前提下使用 state 和其他特性,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。
二. React Hooks解决的问题是?
状态共享,避免嵌套。用于解决长时间使用和维护react过程中常遇到的问题。
1.难以重用和共享组件中的与状态相关的逻辑。
2.逻辑复杂的组件难以开发与维护,当我们的组件需要处理多个互不相关的 local state 时,每个生命周期函数中可能会包含着各种互不相关的逻辑在里面。
3.类组件中的this增加学习成本,类组件在基于现有工具的优化上存在些许问题。
4.由于业务变动,函数组件不得不改为类组件等等。
三. 常见的hooks
1.useState状态钩子
useState()用于为函数组件引入状态(state)。纯函数不能有状态,所以把状态放在钩子里面。
原本写法
export default class Button extends Component {
constructor() {
super();
this.state = { buttonText: "Click me, please" };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(() => {
return { buttonText: "Thanks, been clicked!" };
});
}
render() {
const { buttonText } = this.state;
return <button onClick={this.handleClick}>{buttonText}</button>;
}
}
hooks写法
export default function Button() {
const [buttonText, setButtonText] = useState("Click me, please");
function handleClick() {
return setButtonText("Thanks, been clicked!");
}
return <button onClick={handleClick}>{buttonText}</button>;
}
使用:
首先引入useState,然后声明一个叫 “buttonText” 的 state 变量,读取 buttonText,最后setButtonText修改。
注意事项:
- 在函数组件中,没有 this,所以不能分配或读取 this.state。直接在组件中调用 useState Hook,调用 useState 方法的时候会定义一个 “state 变量”,它与 class 里面的 this.state 提供的功能完全相同。一般来说,在函数退出后变量就会”消失”,而 state 中的变量会被 React 保留。
- useState()函数返回一个数组,数组的第一个成员是一个变量(buttonText),指向状态的当前值。第二个成员是一个函数,用来更新状态,约定是set前缀加上状态的变量名(setButtonText)
2.useEffect():副作用钩子
useEffect()用来引入具有副作用的操作,最常见的就是向服务器请求数据。代替componentDidMount 和 componentDidUpdate。
const Person = ({ personId }) => {
const [loading, setLoading] = useState(true);
const [person, setPerson] = useState({});
useEffect(() => {
setLoading(true);
fetch(`https://swapi.co/api/people/${personId}/`)
.then(response => response.json())
.then(data => {
setPerson(data);
setLoading(false);
});
}, [personId]);
if (loading === true) {
return <p>Loading ...</p>;
}
return (
<div>
<p>You're viewing: {person.name}</p>
<p>Height: {person.height}</p>
<p>Mass: {person.mass}</p>
</div>
);
};
function App() {
const [show, setShow] = useState("1");
return (
<div className="App">
<Person personId={show} />
<div>
Show:
<button onClick={() => setShow("1")}>Luke</button>
<button onClick={() => setShow("2")}>C-3PO</button>
</div>
</div>
);
}
使用方式:
useEffect()接受两个参数。第一个参数是一个函数(剪头函数),异步操作的代码放在里面。第二个参数是一个数组,用于给出 Effect 的依赖项([personId]),只要这个数组发生变化,useEffect()就会执行。第二个参数可以省略,这时每次组件渲染时,就会执行useEffect()。
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Specify how to clean up after this effect:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
解绑副作用:
effect 中返回一个函数是effect 可选的清除机制。每个 effect 都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起。它们都属于 effect 的一部分。React 会在组件卸载的时候执行清除操作。effect 在每次渲染的时候都会执行。这就是为什么 React 会在执行当前 effect 之前对上一个 effect 进行清除。
3.useContext():共享状态钩子
用于在组件之间共享状态
const AppContext = React.createContext({});
const Navbar = () => {
const { username } = useContext(AppContext)
return (
<div className="navbar">
<p>AwesomeSite</p>
<p>{username}</p>
</div>
)
}
const Messages = () => {
const { username } = useContext(AppContext)
return (
<div className="messages">
<h1>Messages</h1>
<p>1 message for {username}</p>
<p className="message">useContext is awesome!</p>
</div>
)
}
function App() {
return (
<AppContext.Provider value={{
username: 'superawesome'
}}>
<div className="App">
<Navbar />
<Messages />
</div>
</AppContext.Provider>
);
}
使用方式:
首先引入useContext,然后使用 React Context API,在组件外部建立一个 Context(AppContext),AppContext.Provider提供了的 Context 对象能被子组件共享,useContext()钩子函数用来引入 Context 对象,从中获取username属性。
4.useReducer():action 钩子
useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。
在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为可以向子组件传递 dispatch 而不是回调函数 。
//const initialState = {count: 0};
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}) {
const [state, dispatch] = useReducer(reducer, initialCount, init);
return (
<>
Count: {state.count}
<button
onClick={() => dispatch({type: 'reset', payload: initialCount})}>
Reset
</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
useReducer(reducer, initialArg, init)第一个是名字,第二个是初始值,第三个是惰性初始化(将 init 函数作为 useReducer 的第三个参数传入,这样初始 state 将被设置为 init(initialArg)。
这么做可以将用于计算 state 的逻辑提取到 reducer 外部,这也为将来对重置 state 的 action 做处理提供了便利),第三个参数可以不写,那就需要第二个参数有初始值。
使用方式:
useReducer声明初始值,dispatch调用reducer。
5.自定义的 Hook
自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook。
import { useState, useEffect } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
使用方式:
useFriendStatu()是一个自定义的 Hook,直接引用传值就行。
注意事项:
- 自定义 Hook 必须以 “use” 开头。
- 在两个组件中使用相同的 Hook 不会共享 state 。
- 每次调用 Hook,它都会获取独立的 state。