React顶层对象下的常用api
isValidElement()createElement()cloneElement()React.Children下的方法React.createRef()React.forwardRef()React.memo()
isValidElement()
React.isValidElement(object) 判断object是否为 React 元素,返回值为 true 或 false。
举个例子
<div>
<h1 id='aa' onClick={handle}>
{name}
</h1>
<div>
{
React.Children.map(children, (child) => {
const Ele = React.cloneElement(child, {
onClick: handle,
style: {...child.props.style, height: '100px', width: '100px'}
})
return Ele;
})
}
</div>
</div>
const handle = (e) => {
console.log(React.isValidElement(document.getElementById('aa')));//false
console.log(React.isValidElement(children)); // true;
}
源码剖析
只有同时满足以下三个条件才会返回true:
1、必须是对象
2、不能为null
3、对象中要有$$typeof 属性,且值必须为 REACT_ELEMENT_TYPE这样的一个常量值。它是一个Symbol值或者16进制的数值。
createElement()
在react@16.x版本之前我们必须在react项目中的每一个组件中主动引入 import React from 'react' 否则会出现以下错误。'React' must be in scope when using JSX 这是因为我们在使用jsx语法时无法直接将其转换为js代码,而是通过 @babel/preset-react将其转换成js代码,这里用到了React.createElement所以我们需要显示的引入React顶层对象。
在react@17.x版本后,如果仅仅使用jsx语法,可以不用主动引入React顶层对象。
React.createElement(type, config,...children) 的参数
- type:要创建的 React 元素类型,可以是
标签名称字符串,也可以是React组件类型,也可以是Reactfragment类型。 - config:写在标签上的属性的集合,js 对象格式,若标签上未添加任何属性则为 null。
- children: 为创建的React元素添加子节点。可以嵌套React.createElement创建的元素。
举个例子
//仅仅为了举例,代码并不严谨
React.useEffect(() => {
const bot = React.createElement('h1', {
style: { color: 'red' },
},
'hello Yu',
React.createElement('div', {style:{color: 'blue'}}, 'hello one!'),,
React.createElement('div', {}, 'hello two!')
)
// ReactDOM.render(bot, document.getElementById('bottom'))
const container = document.getElementById('bottom');
const box = ReactDOM.createRoot(container)
box.render(bot);
}, []);
效果如下:
cloneElement()
cloneElement(ReactElement, config, ...children) 参数与createElement()类似
- ReactElement:要克隆的 React 元素类型,可以是
标签名称字符串,也可以是React组件类型,也可以是Reactfragment类型。 - config:为被克隆元素新添加的属性的集合,js 对象格式,若不需要添加任何属性则为 null。
- children: 为被克隆的React元素添加子节点。会覆盖原元素的子节点。
React.useEffect(() => {
const ret = React.cloneElement(<List/>, null, React.createElement('div', null, '12312'));
const root = ReactDOM.createRoot(document.getElementById('bottom'));
root.render(ret);
}, []);
总结
对于React.cloneElement我的理解是,这个api在封装一些公用组件的时候是非常好用的。
我们常常书写组件的时候的格式大多数是这种
<Conatiner>
<InnerBox/>
</Container>
对于Container和InnerBox两个组件我们都可以根据需求书写为公用组件。
如果我的Container在很多页面上都会添加类似的html dom结构和css样式结构,并需要对内部的组件进行进一步的封装例如为其中每个元素添加 click事件、pointer样式,根据不同需求设置不同的background背景色。(虽然可以通过事件代理或者是分别添加样式可以解决,但使用React.cloneElement足够优雅)
React.Children下的方法
通常props.children 对于我们来说是一个黑盒的存在,我们无法确认他的具体类型是什么
- 如果当前组件没有任何子节点,它就是
undefined。 - 如果有一个子节点,数据类型是
object。 - 如果有多个子节点,数据类型就是
array。
所以为了防止意外的错误情况,React.Children提供了以下几种方法来解决这个问题。
这几种方法对于传入上方的值的类型,都会进行对应的处理,规避了因props.children类型的不同而产生的意外情况。
-
React.Children.map(children, function(child) {})同数组下的map方法,返回一个新的数组。常用渲染props.children时候。 -
React.Children.forEach(children, function(child) {})同数组下的forEach方法,单纯遍历不返回结果。 -
React.Children.count(children)返回子结点的个数。 -
React.Children.only(children)验证 children 是否只有一个子节点(一个 React 元素),如果有则返回它,否则此方法会抛出错误。不接受React.Children.map的返回值,因为其返回值是数组不是React元素。React.Children.only expected to receive a single React element child. -
React.Children.toArray(children)将children这个复杂的数据结构以数组的方式扁平展开并返回,并为每个子节点分配一个 key。当你想要在渲染函数中操作子节点的集合时,它会非常实用,特别是当你想要在向下传递this.props.children之前对内容重新排序或获取子集时。(官方介绍)简单说就是,不管是上面所说的三种类型的哪一种,最终都返回一个数组。
React.createRef()
React.createRef 创建一个能够通过 ref 属性附加到 React 元素的 ref
ref: 在React典型数据流以外,你可能需要强制的去修子组件。被修改的子组件可能是React组件的实例, 也可能是一个DOM元素。
何时使用ref呢?
- 在引入第三方DOM库时,我们常常需要在某个元素下去展示,一般DOM库会需要我们将该元素传递给某个方法,这时候我们往往通过ref来绑定指定的元素,并将其传给该方法。
- 在一些无法通过声明式方式完成的效果时候,我们可以使用ref。例如跳转某个页面时候,我们需要将焦点放在输入框中,这是无法使用声明式来完成的效果,这时候我们就可以通过ref获取input框,在进入该页面后,通过ref调用该元素的focus方法来获取焦点。
- 触发强制动画。
ref之间的不同
- 当创建的ref应用于HTML元素时,构造函数中使用React.createRef创建的ref接受DOM元素作为气current属性。
- 当创建的ref应用于class声明的组件时,ref对象接收组建的挂载实例作为其current属性。
- 不能使用ref在function函数时组件上,因为函数时组件没有实例的概念。但我们可以通过React.forwardRef进行转发,转发给子组件的真实DOM元素或者类式声明的组件。
React.forwardRef()
在开发中我会遇到在父元素中使用创建ref获取该组件下某个子组件下的dom元素来完成一些效果。这时候我们就可以使用这个方法。
效果
代码
const Fun = React.forwardRef((props, ref) => {
return (
<div style={{ color: 'red' }}>
<input ref={ref}></input>
</div>
)
})
export default Fun;
export default function List() {
const newRef = React.createRef(null);
React.useEffect(() => {
newRef.current.value = '12341234123'
newRef.current.focus();
console.log(newRef);
}, []);
return (
<div>
<h1>hello world!</h1>
<Fun ref={newRef}>
</Fun>
</div>
)
}
React.memo()
React.memo() 是一种使用缓存来提升性能的一种手段。
如果你的某个组件在相同的props的情况下渲染相同的结果,那么你可以通过包装在React.memo 中调用。这意味着在这种情况下,React将跳过渲染组件的操作并直接复用最近一次的渲染结果。
其只会检查传入的props的变化,如果该组件被React.memo包裹,但其内部使用了useState、useReducer,useContext的hook,当state和context发生变化,组件仍会重新渲染。
默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。
代码
当点击时只会更新父组件,而子组件并不会卸载。
export default function List() {
const value = [1, 2, 3, 4, 5];
const [state, setState] = React.useState(0);
console.log('father');
return (
<div>
<div onClick={() => setState(pre => pre + 1)}>{state}</div>
<Fun value={value}></Fun>
</div>
)
}
export default React.memo(function Fun(props) {
const { value } = props;
console.log('son');
return (
<div style={{ color: 'red' }}>
{value.map(item => <div key={item}>{item}</div>)}
</div>
)
}, (pre, cur) => {
return JSON.stringify(pre.value.sort()) === JSON.stringify(cur.value.sort());
})