可以在 function component
中使用 hooks,如 useState、useContext
等,让它具有 class
才具有的一些特性。并且在 class
中,hooks
是不管用的,因此不能使用。
state hooks
import React, { useState } from 'react';
function Example(props) {
// useState 返回一对:当前状态值和一个允许更新它的函数, 这一对的名字可以随便取,并且不会像 class 中的 state 必须是一个 object。可以是一个普通的值。
const [count, setCount] = useState(0/* initial state*/);
// 可以调用多次,生成多个 state, 它们之间互不影响。
const [person, upPerson] = useState({ name: 'longjicnen', age: 21}/* initial state*/);
return (
<div>
<button onClick={() => this.setState(count + 1 )}>
Click me
</button>
<button onClick={() => this.setState({ age: person.age + 1 })}>
Click me
</button>
</div>
)
}
effect hook
从 React 组件获取数据、订阅或手动更改 DOM。我们称这些手术为“side effect”。
它的作用与 React 类中的componentDidMount、componentDidUpdate、componentWillUnmount
相同, 是它们的一个合集。当你调用 useEffect
的时候,默认的会在每次更新之后调用 effect
,包括第一次 render
。
React 组件中有两种常见的副作用: 不需要清理的副作用和需要清理的副作用。
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// 默认情况下在每次 render 完成后都会调用,包括第一次挂载和更新,并且根据 javascript 机制,每一次都是不同的 effect 函数。effect 会延迟到浏览器绘制完成后再执行。
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
document.title = `You clicked ${count} times`;
return () => {
// 如果我们返回一个函数,会在 每次 render 和 unmount 的时候执行,我们可以在这个函数里面执行解绑逻辑。注意这里不止是在 unmount 执行。
}
// 可以声明多个 effect, 会按照定义顺序调用,这允许我们将代码分割到不同的函数中,而不是全部放到一个生命周期里面。
useEffect(() => {
// featch data
});
useEffect(() => {
// featch data
}, [count/* 这里作为第二个参数,是为了性能优化,为了避免每一次 re-render 都执行effect, 可以提供一个数组,里面的数据如果前后 render 没有改变,那么就会自动跳过这个 effect, 如果为 [], 那么只会在 mount and unmount 阶段执行, 并且在此 effect 中, props 或者 states 在其中一直都是最初的值。*/]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
1.总是在function component
顶部调用 hooks
, 不要在循环、条件或嵌套函数中调用钩子。
2. 只在 function components.
中调用 hooks
3. hooks 每次 render 调用的时候,会对应到上次调用的顺序,如果放到条件语句中,如某一次不执行,会打乱原先的顺序,使得后面一个 hooks 对应到当前的 hooks。
自定义 hooks 是指我们定义一个 function 名字以 use
开头, 并且将一些公用逻辑放到该方法中,调用一些 React
定义的 hooks
。类似于高阶组件和 render props
。
需要注意的时候, customs hooks
复用的是 logic
,每次你使用它的时候,state、effect
等 hooks
都是全新的。
import React, { 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 FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id);
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
// 一般来说 MyContext 需要公用,可以定义到一个文件中,然后再需要用到 useContext 的地方引入,该 context
const MyContext = React.createContext()
// 会向上查找使用该 context.provider 提供值的地方,并返回该值。只要该值发生改变,那么会触发使用下面这行代码的组件的rerender
const value = useContext(MyContext);
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState, init/*如果穿了这个方法,那么初始值会变为 init(initialArg) */);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
);
}
返回一个 callback
// 只有当数组中的依赖发生改变了之后 memoizedCallback 才会发生改变。所有 callback 内部引用到的变量,都需要出现在后面的数组中。
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
返回一个 value
,类似于 vue
的计算属性。
// 如果后面的数组的值为 [] 或者没有指定,那么每次 render 的时候都会计算。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
const refContainer/*refContainer.current等于initalValue*/ = useRef(initialValue);
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}