网站首页 > 开源技术 正文
Refs and the DOM
何时使用 Refs
下面是几个适合使用 refs 的情况:
- 管理焦点,文本选择或媒体播放。
- 触发强制动画。
- 集成第三方 DOM 库。
避免使用 refs 来做任何可以通过声明式实现来完成的事情。
举个例子,避免在 Dialog 组件里暴露 open() 和 close() 方法,最好传递 isOpen 属性。
勿过度使用 Refs
你可能首先会想到使用refs 在你的 app 中“让事情发生”。如果是这种情况,请花一点时间,认真再考虑一下 state 属性应该被安排在哪个组件层中。通常你会想明白,让更高的组件层级拥有这个 state,是更恰当的。---状态提升
你不能在函数组件上使用 ref 属性,因为他们没有实例。函数式组件里的dom元素是可以添加ref的,组件不能
在高阶组件中转发 refs
refs 将不会透传下去。这是因为 ref不是 prop 属性。就像 key一样,其被 React 进行了特殊处理。如果你对 HOC 添加 ref,该 ref 将引用最外层的容器组件,而不是被包裹的组件。
幸运的是,我们可以使用React.forwardRefAPI 明确地将 refs 转发到内部的组件或者元素
function logProps(Component) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}
render() {
const {forwardedRef, ...rest} = this.props;
// 将自定义的 prop 属性 “forwardedRef” 定义为 ref
return <Component ref={forwardedRef} {...rest} />;
}
}
// 注意 React.forwardRef 回调的第二个参数 “ref”。
// 我们可以将其作为常规 prop 属性传递给 LogProps,例如 “forwardedRef”
// 然后它就可以被挂载到被 LogProps 包裹的子组件上。
return React.forwardRef((props, ref) => {
return <LogProps {...props} forwardedRef={ref} />;
});
}
Refs 与函数组件
默认情况下,你不能在函数组件上使用 ref 属性,因为它们没有实例:
如果要在函数组件中使用 ref,你可以使用 forwardRef(可与 useImperativeHandle结合使用),或者可以将该组件转化为 class 组件。
不管怎样,你可以在函数组件内部使用 ref 属性,只要它指向一个 DOM 元素或 class 组件:
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
回调 Refs
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = null;
this.setTextInputRef = element => {
this.textInput = element;
};
this.focusTextInput = () => {
// 使用原生 DOM API 使 text 输入框获得焦点
if (this.textInput) this.textInput.focus();
};
}
componentDidMount() {
// 组件挂载后,让文本框自动获得焦点
this.focusTextInput();
}
render() {
// 使用 `ref` 的回调函数将 text 输入框 DOM 节点的引用存储到 React
// 实例上(比如 this.textInput)
return (
<div>
<input
type="text"
ref={this.setTextInputRef}
/>
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
React 将在组件挂载时,会调用 ref 回调函数并传入 DOM 元素,当卸载时调用它并传入 null。在 componentDidMount 或 componentDidUpdate 触发前,React 会保证 refs 一定是最新的。
如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。
你可以在组件间传递回调形式的 refs,就像你可以传递通过 React.createRef() 创建的对象 refs 一样。
function CustomTextInput(props) {
return (
<div>
<input ref={props.inputRef} />
</div>
);
}
class Parent extends React.Component {
render() {
return (
<CustomTextInput
inputRef={el => this.inputElement = el}
/>
);
}
}
Render Props
术语 “render prop”是指一种在React 组件之间使用一个值为render函数的 prop 共享代码的简单技术
具有 render prop 的组件接受一个函数,该函数返回一个 React 元素并调用它而不是实现自己的渲染逻辑。
<DataProvider render={data => (<h1>Hello {data.target}</h1>)}/>
使用 render prop 的库有 React Router、Downshift以及 Formik。
在这个文档中,我们将讨论为什么 render prop 是有用的,以及如何写一个自己的 render prop 组件。
使用 Render Props 来解决横切关注点(Cross-Cutting Concerns)
这也是 render prop 的来历:我们可以提供一个带有函数 prop 的 <Mouse> 组件,它能够动态决定什么需要渲染的,而不是将 <Cat> 硬编码到 <Mouse> 组件里,并有效地改变它的渲染结果。
import React, { PureComponent } from 'react'
class Cat extends React.Component {
render() {
const mouse = this.props.mouse;
return (
<img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
);
}
}
class Mouse extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
{/*
Instead of providing a static representation of what <Mouse> renders,
use the `render` prop to dynamically determine what to render.
*/}
{this.props.render(this.state)}
</div>
);
}
}
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>移动鼠标!</h1>
<Mouse render={mouse => (
<Cat mouse={mouse} />
)}/>
</div>
);
}
}
export default MouseTracker
重要的是要记住,render prop 是因为模式才被称为 renderprop ,你不一定要用名为 render的 prop 来使用这种模式。事实上, 任何被用于告知组件需要渲染什么内容的函数 prop 在技术上都可以被称为 “render prop”
注意事项
将 Render Props 与 React.PureComponent 一起使用时要小心
如果你在 render 方法里创建函数,那么使用 render prop 会抵消使用 React.PureComponent 带来的优势。因为浅比较 props 的时候总会得到 false,并且在这种情况下每一个 render 对于 render prop 将会生成一个新的值。
为了绕过这一问题,有时你可以定义一个 prop 作为实例方法,类似这样:
class MouseTracker extends React.Component {
// 定义为实例方法,`this.renderTheCat`始终
// 当我们在渲染中使用它时,它指的是相同的函数
renderTheCat(mouse) {
return <Cat mouse={mouse} />;
}
render() {
return (
<div>
<h1>Move the mouse around!</h1>
<Mouse render={this.renderTheCat} />
</div>
);
}
}
如果你无法静态定义 prop(例如,因为你需要关闭组件的 props 和/或 state),则 <Mouse>应该扩展 React.Component。
React.memo
const MyComponent = React.memo(function MyComponent(props) {
/* 使用 props 渲染 */
});
React.memo 为高阶组件。它与 React.PureComponent非常相似,但只适用于函数组件,而不适用 class 组件。
React.memo 仅检查 props 变更。如果函数组件被 React.memo 包裹,且其实现中有 useState或 useContext的 Hook,当 context 发生变化时,它仍会重新渲染。
默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。
function MyComponent(props) {
/* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
/*
如果把 nextProps 传入 render 方法的返回结果与
将 prevProps 传入 render 方法的返回结果一致则返回 true,
否则返回 false
*/
}
export default React.memo(MyComponent, areEqual);
与 class 组件中 shouldComponentUpdate()方法不同的是,如果 props 相等,areEqual 会返回 true;如果 props 不相等,则返回 false。这与 shouldComponentUpdate 方法的返回值相反。
Children
map
React.Children.map(children, function[(thisArg)] )
<SafeAreaView style={style} edges={['right', 'bottom', 'left']}>
{React.Children.map(children, child => child)}
</SafeAreaView>
给props.children传参
React.Children.map(this.props.children, child => {
return React.cloneElement(child, {
params: () => {} // your props
});
})
forEach
React.Children.forEach(children, function[(thisArg)])
与 React.Children.map()类似,但它不会返回一个数组。
count
React.Children.count(children)
返回 children中的组件总数量,等同于通过 map或 forEach调用回调函数的次数。
only
React.Children.only(children)
验证 children是否只有一个子节点(一个 React 元素),如果有则返回它,否则此方法会抛出错误。
注意: React.Children.only() 不接受 React.Children.map()的返回值,因为它是一个数组而并不是 React 元素。
toArray
React.Children.toArray(children)
将 children 这个复杂的数据结构以数组的方式扁平展开并返回,并为每个子节点分配一个 key。当你想要在渲染函数中操作子节点的集合时,它会非常实用,特别是当你想要在向下传递 this.props.children 之前对内容重新排序或获取子集时。
注意:
React.Children.toArray() 在拉平展开子节点列表时,更改 key 值以保留嵌套数组的语义。也就是说,toArray 会为返回数组中的每个 key 添加前缀,以使得每个元素 key 的范围都限定在此函数入参数组的对象内。
lazy
React.lazy() 允许你定义一个 动态加载的组件。这有助于缩减 bundle 的体积,并延迟加载在初次渲染时未用到的组件。
// 这个组件是动态加载的
const SomeComponent = React.lazy(() => import('./SomeComponent'));
请注意,渲染 lazy组件依赖该组件渲染树上层的 <React.Suspense>组件。这是指定加载指示器(loading indicator)的方式。
注意 : 使用 React.lazy 的动态引入特性需要 JS 环境支持 Promise。在 IE11 及以下版本的浏览器中需要通过引入 polyfill 来使用该特性。
Suspense
React.Suspense可以指定加载指示器(loading indicator),以防其组件树中的某些子组件尚未具备渲染条件。目前,懒加载组件是 <React.Suspense>支持的唯一
用例:
// 该组件是动态加载的
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
// 显示 <Spinner> 组件直至 OtherComponent 加载完成
<React.Suspense fallback={<Spinner />}>
<div>
<OtherComponent />
</div>
</React.Suspense>
);
}
请注意,lazy 组件可以位于 Suspense 组件树的深处——它不必包装树中的每一个延迟加载组件。最佳实践是将 <Suspense> 置于你想展示加载指示器(loading indicator)的位置,而 lazy() 则可被放置于任何你想要做代码分割的地方。
猜你喜欢
- 2025-01-03 React 性能优化十大总结
- 2025-01-03 Vue和React的一些对比:哪个更适合你?
- 2025-01-03 React 的一些最佳安全实践
- 2025-01-03 搭建一个无需构建工具的 React 页面
- 2025-01-03 支持Vue、React混用的veaury库大火?前端摆脱二选一?
- 2025-01-03 前端开发react框架 - 组件
- 2025-01-03 从0到1无比流畅的React入门教程
- 2025-01-03 如何设计更优雅的 React 组件?
- 2025-01-03 React中使用Ant Table组件
- 2025-01-03 React支持了ES6 Classes,听听他们怎么说
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- jdk (81)
- putty (66)
- rufus (78)
- 内网穿透 (89)
- okhttp (70)
- powertoys (74)
- windowsterminal (81)
- netcat (65)
- ghostscript (65)
- veracrypt (65)
- asp.netcore (70)
- wrk (67)
- aspose.words (80)
- itk (80)
- ajaxfileupload.js (66)
- sqlhelper (67)
- express.js (67)
- phpmailer (67)
- xjar (70)
- redisclient (78)
- wakeonlan (66)
- tinygo (85)
- startbbs (72)
- webftp (82)
- vsvim (79)
本文暂时没有评论,来添加一个吧(●'◡'●)