前言
react hooks 是 React 16.8 的新增特性。 它可以让我们在函数组件中使用 state 、生命周期以及其他 react特性,而不仅限于 class 组件。react hooks 的出现,标示着 react中不会在存在无状态组件了,只有类组件和函数组件。具体可查看官网。
优势:
- 函数组件不能使用state,遇到交互更改状态等复杂逻辑时不能更好地支持,hooks让函数组件更靠近class组件,拥抱函数式编程。
- 解决副作⽤问题,hooks出现可以处理数据获取、订阅、定时执行任务、手动修改 ReactDOM这些⾏为副作用,进行副作用逻辑。比如useEffect。
- 更好写出有状态的逻辑重用组件。
- 让复杂逻辑简单化,比如状态管理:useReducer、useContext。
- 函数式组件比class组件简洁,开发的体验更好,效率更⾼,性能更好。
- 更容易发现无用的状态和函数。
useImperativeHandle介绍
useImperativeHandle(ref, createHandle, [deps])
useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一起使用。
- ref:定义 current 对象的 ref createHandle:一个函数,返回值是一个对象,即这个 ref 的 current
- 对象 [deps]:即依赖列表,当监听的依赖发生变化,useImperativeHandle 才会重新将子组件的实例属性输出到父组件
- ref 的 current 属性上,如果为空数组,则不会重新输出。
useImperativeHandle使用
在介绍 useImperativeHandle 之前一定要清楚 React 关于 ref 转发(也叫透传)的知识点,是使用 React.forwardRef 方法实现的,该方法返回一个组件,参数为函数(props callback,并不是函数组件),函数的第一个参数为父组件传递的 props,第二给参数为父组件传递的 ref,其目的就是希望可以在封装组件时,外层组件可以通过 ref 直接控制内层组件或元素的行为。
一个关于 ref 转发的例子
import React, { useCallback, useRef } from 'react';
import ReactDOM from 'react-dom';
// 实现 ref 的转发
const FancyButton = React.forwardRef((props, ref) => (
<div>
<input ref={ref} type="text" />
<button>{props.children}</button>
</div>
));
// 父组件中使用子组件的 ref
function App() {
const ref = useRef();
const handleClick = useCallback(() => ref.current.focus(), [ ref ]);
return (
<div>
<FancyButton ref={ref}>Click Me</FancyButton>
<button onClick={handleClick}>获取焦点</button>
</div>
)
}
ReactDOM.render(<App />, root);
上面例子中创建了一个 FancyButton 组件,内部渲染了一个 button 元素,我们希望在父元素 App 中渲染 FancyButton,并通过传递给 FancyButton 的 ref 直接操作内部的 button。
一个官方的 useImperativeHandle 例子
import React, { useRef, useImperativeHandle } from 'react';
import ReactDOM from 'react-dom';
const FancyInput = React.forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} type="text" />
});
const App = props => {
const fancyInputRef = useRef();
return (
<div>
<FancyInput ref={fancyInputRef} />
<button
onClick={() => fancyInputRef.current.focus()}
>父组件调用子组件的 focus</button>
</div>
)
}
ReactDOM.render(<App />, root);
上面这个例子中与直接转发 ref 不同,直接转发 ref 是将 React.forwardRef 中函数上的 ref 参数直接应用在了返回元素的 ref 属性上,其实父、子组件引用的是同一个 ref 的 current 对象,官方不建议使用这样的 ref 透传,而使用 useImperativeHandle 后,可以让父、子组件分别有自己的 ref,通过 React.forwardRef 将父组件的 ref 透传过来,通过 useImperativeHandle 方法来自定义开放给父组件的 current。
useImperativeHandle 的第一个参数是定义 current 对象的 ref,第二个参数是一个函数,返回值是一个对象,即这个 ref 的 current 对象,这样可以像上面的案例一样,通过自定义父组件的 ref 来使用子组件 ref 的某些方法。
useImperativeHandle 和 React.forwardRef 必须是配合使用的,这也是为什么在开头要介绍 ref 的转发。
import React, {
useState,
useRef,
useImperativeHandle,
useCallback
} from 'react';
import ReactDOM from 'react-dom';
const FancyInput = React.forwardRef((props, ref) => {
const [ fresh, setFresh ] = useState(false)
const attRef = useRef(0);
useImperativeHandle(ref, () => ({
attRef,
fresh
}), [ fresh ]);
const handleClick = useCallback(() => {
attRef.current++;
}, []);
return (
<div>
{attRef.current}
<button onClick={handleClick}>Fancy</button>
<button onClick={() => setFresh(!fresh)}>刷新</button>
</div>
)
});
const App = props => {
const fancyInputRef = useRef();
return (
<div>
<FancyInput ref={fancyInputRef} />
<button
onClick={() => console.log(fancyInputRef.current)}
>父组件访问子组件的实例属性</button>
</div>
)
}
ReactDOM.render(<App />, root);
上面的案例相对于官方的例子意图更明显一些,通过 useImperativeHandle 将子组件的实例属性输出到父组件,而子组件内部通过 ref 更改 current 对象后,组件不会重新渲染,需要改变 useState 设置的状态才能更改。