Blog
Blog copied to clipboard
react16.8 HOOKS
HOOKS
Hook是React 16.8中的新增功能。它们允许您在不编写类的情况下使用状态和其他React功能。HOOKS只能在函数组件中使用
memo
React.memo 是一个高阶的组件。它类似于React.PureComponent 也就是说如果组件的 props 没有改变,是不会被重新渲染的。
function Foo (props) {
...
}
export default React.memo(Foo)
useState
类似于类组件中的state,不同的是 useState 接受一个任意类型的值 string, array, object, bool... 作为参数并返回一个数组,且 useState 只会在组件初始化的时候执行。
// 初始化的时候,age的值就是useState中参数的值
const [ age, setAge ] = useState(20);
const [ visible, setVisible ] = useState(props.visible);
数组中的第一个元素是状态值,组件在运行过程中会保留这个状态值,类似于this.state 数组中的第二个元素是改变这个状体值的函数,类似于this.setState()
function Hooks(props) {
const [ age, setAge ] = useState(20);
const [ visible, setVisible ] = useState(props.visible);
return (
<div className="">
<p>我的年龄是{age}岁</p>
<button onClick={() => setAge(age + 1)}>点击</button>
<p>{`${visible}`}</p>
</div>
);
};
useEffect
这个类似类组件中的 componentDidMount 和 componentDidUpdate。每次当函数组件挂载成功或者重新渲染完成后都会调用 useEffect 。 之所以说类似,是因为 useEffect 不完全同类组件中的 componentDidMount 和 componentDidUpdate生命周期函数一样,useEffect 有延迟,在父组件didMount或didUpdate后,但在任何新渲染之前触发。useEffect可以在组件中使用多次。
useEffect 还可以返回一个函数,并在组件即将销毁时调用这个返回函数,没错,就是和类组件的 componentWillUnmount 一样。

function Hooks(props) {
const [ age, setAge ] = useState(20);
// 当组件挂载成功后调用下面的函数
// 当props.visible 改变了,那么会在组件重新渲染完成以后调用下面的函数
// 当调用setAge,age发生改变,那么会在组件重新渲染完成以后调用下面的函数
useEffect(() => {
console.log(props);
// 会在组件willUnmount时候调用
return () => {...}
});
return (
<div className="">
<p>我的年龄是{age}岁</p>
<button onClick={() => setAge(age + 1)}>点击</button>
<p>{`${visible}`}</p>
</div>
);
};
useEffect也可以接收一个数组作为第二个参数
类似于类组件中的 componentDidUpdate(prevProps, prevState) ,这个生命周期,那么如何使用呢?
function Hooks(props) {
const [ age, setAge ] = useState(20);
const [ visible, setVisible ] = useState(props.visible);
// 当函数调用时发现props.visible发生了变化,类似于类组件中的componentDidUpdate(prevProps, prevState)
// 当prevProps.visible !== this.props.visible 那么就会执行useEffect的函数体
useEffect(() => {
console.log('visible is changed');
setVisible(props.visible);
}, [ props.visible ]);
// 当函数调用时发现age发生了变化,类似于类组件中的componentDidUpdate(prevProps, prevState)
// 当prevState.age !== this.state.age 那么就会执行useEffect的函数体
useEffect(() => {
console.log(age, 1111);
}, [ age ]);
return (
<div className="">
<p>我的年龄是{age}岁</p>
<button onClick={() => setAge(age + 1)}>点击</button>
<p>{`${visible}`}</p>
</div>
);
};
如果参数中有多个元素 [ age, props.visible ] ,那么元素的关系是 age && props.visible ,通过比较后只要有一个元素发生变化,useEffect 就会执行。如果参数是一个空数组 [] ,那么这个时候 useEffect 就和类组件中的 componentDidMount 一样,只在组件刚刚挂载的时候调用一次。 useEffect 函数中return的函数,不受第二个参数的影响,仍在组件 WillUnmount 的时候调用。
不要在循环条件或嵌套函数中调用Hook。相反,始终在React函数的顶层使用Hooks。通过遵循此规则,您可以确保每次组件呈现时都以相同的顺序调用Hook。这就是React允许多个useState和useEffect调用之间正确保留Hook状态的原因。
useLayoutEffect
和 useEffect 使用原理相同,但是唯一的区别在于 useLayoutEffect 不会延迟触发,和类组件的 componentDidMount 和 componentDidUpdate 这两个生命周期函数是同步的,其他没有区别。
customize hooks
自定义Hook是一个JavaScript函数,其名称以“use” 开头,可以调用其他Hook。构建自己的Hook可以将组件逻辑提取到可重用的函数中 ,确保只在自定义Hook的顶层无条件地调用其他Hook。与React组件不同,自定义Hook不需要具有特定签名。我们可以决定它作为参数需要什么,以及它应该返回什么(如果有的话)
// useVisibleStatus是一个自定义的钩子,我们在函数中调用的useEffect
function useVisibleStatus(isShow) {
const [ visible, setVisible ] = useState(isShow);
useEffect(() => {
setVisible(isShow);
}, [ isShow ]);
return visible;
};
function Hooks(props) {
const [ count ] = useState(props.count);
const visible = useVisibleStatus(props.visible);
return (
<div className="">
<h2>{count}</h2>
<button onClick={() => setCount(count + 1)}>点击</button>
<h2>{`${visible} ${props.count}`}</h2>
</div>
);
};
我们也可以将一些复杂或者重复的逻辑提取提取到自定义的hook函数中,从而简化我们的代码。其实自定义hook和函数组件没有多大区别。
useReducer
当 useState 复杂的状态逻辑涉及多个子值或下一个状态取决于前一个状态时,通常useReducer更可取。useReducer还可以让您优化触发深度更新的组件的性能
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({initialState}) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
);
}
我们看看 useReducer 具体的实现(和自定义hook没有差异):
function useReducer(reducer, initialState) {
const [state, setState] = useState(initialState);
function dispatch(action) {
const nextState = reducer(state, action);
setState(nextState);
}
return [state, dispatch];
}
useImperativeHandle
可以通过 useImperativeHandle ,给ref上绑定一些自定的事件,前提是我们必须使用 forwardRef ,注意所有的事件都是绑定在ref的 current 属性上。
看下面的例子
// hook.js
function Hooks(props, ref) {
const [ count, setCount ] = useState(props.count);
useImperativeHandle(ref, () => ({
// 自定义一些事件
click: () => {
setCount(count + 1);
},
}));
return (
<div className="">
<h2>{count}</h2>
<button onClick={() => setCount(count + 1)}>点击</button>
</div>
);
};
export default React.forwardRef(Hooks);
// Application.js
export default class App extends PureComponent {
componentDidMount() {
this.ref = React.createRef();
}
return (
<div
onClick={() => this.ref.current.click()}
>
// ...
<Hooks ref={this.ref} count={this.state.count} visible={this.state.visible}/>
// ...
</div>
);
}
或者
function FancyInput(props, ref) {
// 获取真是DOM节点
const inputRef = useRef();
useImperativeHandle(ref, () => ({
// 自定义一些事件
focus: () => {
// 在DOM节点执行一些操作都可以
inputRef.current.focus();
}
}));
return <input ref={inputRef} />;
}
FancyInput = forwardRef(FancyInput);
useRef
useRef 返回一个可变的ref对象,其 .current 属性值为初始化传递的参数(initialValue)。返回的对象将持续整个组件的生命周期。和class组件中的实例属性很像
const ref = usRef(20);
console.log(ref.current) // 20
// 可以重新赋值
ref.current = 200;
当然最常见的就是访问一个元素节点
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>
</>
);
}
useMemo
使用的场景:函数组件中,我们定义了一些方法,但是我们并不希望每次组件更新的时候都重新执行一次个函数,而是变成有条件的触发,那么这个时候我们就可以使用useMemo。有个地方需要注意点那就是,useMemo是在useLayoutEffect之前执行,这和类组件中的 componentWillMount 和 componentWillUpdate 类似。可以查看的我们demo
// 组件初始化的时候会调用Func,类似 componentWillMount
// 当数组中的元素的值发生改变,那么就会调用Func,这个条件 `a && b` 的关系
useMemo(() => Func(a, b), [a, b]);
在看这个带返回值的
function Hooks(props) {
const [ count, setCount ] = useState(props.count);
useLayoutEffect(() => {
console.log('useLayoutEffect 后执行');
setCount(props.count);
}, [ props.count ]);
const dom = useMemo(() => {
console.log('useMemo 优先执行');
return <h2>{count * 10}</h2>;
}, [count]);
return (
<div className="">
<h2>{count}</h2>
<button onClick={() => setCount(count + 1)}>点击</button>
{dom}
</div>
);
};
useCallback
useCallback 的使用和 useMemo 是一样的,且 useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。
这是我的demo