一、 React Hook 的 简介:
Hook 是React 16.8的新增特性, 它可以让你在不编写class 的情况下使用statet 以及其他的React特性,Hook 使你在无需修改组件结构的情况下复用状态逻辑; 它将组件中相互关联的部分拆分成更小的函数(比如设置订阅或者请求数据),而非强制按照生命周期划分。
二、Hook解决的问题:
- (1)、函数式组件,比class 更容易理解;
- (2)、无需修改组件结构 复用状态逻辑,UI 和 逻辑更容易分离;
- (3)、将组件相互关联的部分拆分成更小的函数,无生命周期;
- (4)、避免地狱式嵌套,可读性提高;
- (5)、解决HOC 和 Render Props 的缺点。
三、 useState
state 的初始值为 { count : 0 }, 当用户点击按钮之后,我们通过调用this.setState() 来增加 state.count。
函数组件
(Hook 在 class 内部是不起作用的)
import React, { useState } from 'react';
function Test() {
cosnt [count, setCount] = useStaet(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me </button>
</div>
);
}
useState 需要哪些参数? useState() 方法里面唯一的参数就是初始 state。不同于 class 的是,我们可以按照需要使用数字或字符串对其进行赋值,而不一定是对象。在示例中,只需使用数字来记录用户点击次数,所以我们传了 0 作为变量的初始 state。(如果我们想要在 state 中存储两个不同的变量,只需调用 useState() 两次即可。)
四、 useEffect
Effect Hook 可以让你在函数组件中执行副作用操作;
import React, { useState, useEffect } from 'react'; function Test() { const [count, setCount] = useState(0); useEffect( () => { document.title = `You clicked ${count} times`; }); return ( <div> <p>You cliked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me </button> </div> ); }
useEffect Hook 看做成componentDidMount、componentDidUpdate 和 componentWillUnmount 这三个函数的组合。
effect 内部执行是异步的
默认情况下,它在第一次渲染之后和 每次更新之后都会执行。 effect 发生在“渲染之后”,React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。
副作用操作中分为: 需要清除的 和 不需要清除的
(1)、无需清除的 effect
有时候,我们只想在 React 更新 DOM 之后运行一些额外的代码。比如发送网络请求,手动变更 DOM,记录日志,这些都是常见的无需清除的操作。
(2)、需要清除的 effect
例如: 订阅外部数据源, 防止引起内存泄露
react 官网demo -- 使用 Class 的示例
通常会在componeDidMount 中设置订阅,并在componentWillUnmount 中清除它。例如,假设我们有一个ChatAPI 模块, 它允许我们订阅好友的在线状态:
class FriendStatus extends React.Component {
constructor(props) {
super(props);
this.state = { isOnline : null };
this.handleStatusChange = this.handleStatusChange.bind(this);
}
componentDidMount() {
ChatAPI.subscribeToFriendStatus() {
this.porps.friend.id, this.handleStatusChange
}
}
componentWillUnmount() {
ChatAPI.subscribeToFriendStatus() {
this.props.friend.id, this.handleStatsuChange
}
}
handleStatusChange(status) {
this.setState({ isOnline: status.isOnline });
}
render() {
if (this.state.isOnline === null) {
return 'Loading...';
}
return this.state.isOline ? 'Online' : 'Offline';
} }
使用 Hook 的示例:
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
funcion handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
}
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
1)、为什么要在effect 中返回一个函数?
这是effect 可选的清除机制,每个effect 都可以返回一个清除函数; 如此可以将添加和移除订阅的逻辑放在一起,它们都属于effect 的一部分。
2)、React 何时清除effect?
React会在组件卸载的时候执行清除操作, effect 在每次渲染的时候都会执行; 这就是为什么React 会在执行当前effect 之前 对上一个effect 进行清除。
五、 useContext
const value = useContext(MyContext);
接收一个context对象,(React.createContext 的返回值)并返回改context 的当前值。 当前的context 值由上层组件中距离当前组件最近的 <MyContext.Provieder> 的value prop 决定。
当组件上层最近的<MyContext.Provider> 更新时, 该Hook 会触发重渲染,并使用 最新传递给MyContext provider 的context value 值。
useContext(MyContext)
const themes = {
light: {
foreground: "#000",
background: "#eee"
},
dark: {
foreground: "#fff",
background: "#222"
}
};
const ThemeContext = React.createContext(themes.light);
function App() {
return (
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}
六、 useReducer
const [state, dispatch] = useReducer(reducer, initialArg, init);
useReducer 接收类型(state, action)=> newState 的reducer,和redux工作方式一样, 并返回与分派方式配对的当前状态。
reducer 就是一个只能通过action 将 sate 从一个过程转换成一个过程的纯函数;
使用场景:
- (1)、state 逻辑较复杂且包含多个子值,可以集中处理;
- (2)、下一个state 依赖于之前的state;
- (3)、想深层级修改子组件的一些状态,使用useReducer 还能给那些会触发更新的组件做性能优化;
使用reducer 有助于将读取和写入分开。
下面是 useState 部分的反例,重写为使用reducer:
const initialState = { count:0 };
function reducer(state, action) {
switch (action.type) {
case: 'reset':
return initialState;
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: sate.count - 1 };
}
}
function Counter({initialCount}) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: { state.count }
<button onClick={() => dispatch({type: 'reset'})}>Reset</button>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement'})}>-</button>
</>
);
}
七、 useRef
const refContainer = useRef(initialValue);
useRef 返回一个可变ref 对象,其.curretn 属性初始化为传递的参数 (initialValue);
返回的对象将在组将的整个生命周期内保持不变。
=》
useRef 可以保存任何可变的值,其类似于在class 中 使用实例字段的方式。
用法:
(1)、useRef 可以存储那些不需要引起页面重新渲染的数据;
(2)、如果你刻意地想要从某些异步回调中读取/最新的/state, 你可以用 一个ref来保存或修改它,并从中读取。
function TextButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
)
}
八、 useDemo
const memoizedValue = useMemo(() => computeExpensiveValue(a,b), [a,b]);
传递一个“create” 函数 和 一个输入数组,useDemo 只会在其中一个输入发生更改时重新计算memoized。这种优化有助于避免对每个渲染进行昂贵的计算。
如果未提供数组,则每当将新函数实例作为第一个参数传递时,都将计算新值。(在每个渲染上使用内联函数)
import React, { useState, useMemo } from “react”;
function Demo() {
const [count, setCount] = useState(0);
const handle = () => {
return count;
};
const handle1 = useDemo(() => {
console.log('handle1', count);
return count;
}, []);
const handle2 = useDemo(() => {
console.log('handle2', count); //大计算量的方法
return count;
}, [count]);
console.log("render-top");
return (
<div>
<p>
demo: {count}
<button onClick={() => setCount(count + 1)}>++count</button>
</p>
<p>-------------------</p>
<Child handle={handle1} />
</div>
);
}
function Child({ handle }) {
console.log("render-child");
return (
<div>
<p>child</p>
<p>props-data: {handle}</p>
</div>
);
}
export default Demo;
总结:
(1)、如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值;
(2)、useMemo 用于返回 memoize, 防止每次render 的时候,大计算量带来的开销。