接下来我们一起来探究一下react工具类函数的用法。

createElement
一提到createElement,就不由得和JSX联系一起。我们写的jsx,最终会被 babel,用createElement编译成react元素形式。我写一个组件,我们看一下会被编译成什么样子,
如果我们在render里面这么写:
render(){
return <div className="box" >
<div className="item" >生命周期</div>
<Text mes="hello,world" />
<React.Fragment> Flagment </React.Fragment>
{
/* */ }
text文本
</div>
}
会被编译成这样:
render() {
return React.createElement("div", {
className: "box" },
React.createElement("div", {
className: "item" }, "\u751F\u547D\u5468\u671F"),
React.createElement(Text, {
mes: "hello,world" }),
React.createElement(React.Fragment, null, " Flagment "),
"text\u6587\u672C");
}
当然我们可以不用jsx模式,而是直接通过createElement进行开发。
createElement模型:
React.createElement(
type,
[props],
[...children]
)
createElement参数:
第一个参数:如果是组件类型,会传入组件,如果是dom元素类型,传入div或者span之类的字符串。
第二个参数::第二个参数为一个对象,在dom类型中为属性,在组件类型中为props。
其他参数:,依次为children,根据顺序排列。
createElement做了些什么?
经过createElement处理,最终会形成 $$typeof = Symbol(react.element)对象。对象上保存了该react.element的信息。
cloneElement
可能有的同学还傻傻的分不清楚cloneElement和createElement区别和作用。
createElement把我们写的jsx,变成element对象; 而cloneElement的作用是以 element 元素为样板克隆并返回新的 React 元素。返回元素的 props 是将新的 props 与原始元素的 props 浅层合并后的结果。
那么cloneElement感觉在我们实际业务组件中,可能没什么用,但是在一些开源项目,或者是公共插槽组件中用处还是蛮大的,比如说,我们可以在组件中,劫持children element,然后通过cloneElement克隆element,混入props。经典的案例就是 react-router中的Swtich组件,通过这种方式,来匹配唯一的 Route并加以渲染。
我们设置一个场景,在组件中,去劫持children,然后给children赋能一些额外的props:
function FatherComponent({
children }){
const newChildren = React.cloneElement(children, {
age: 18})
return <div> {
newChildren } </div>
}
function SonComponent(props){
console.log(props)
return <div>hello,world</div>
}
class Index extends React.Component{
render(){
return <div className="box" >
<FatherComponent>
<SonComponent name="alien" />
</FatherComponent>
</div>
}
}
打印:

完美达到了效果!
createContext
createContext用于创建一个Context对象,createContext对象中,包括用于传递 Context 对象值 value的Provider,和接受value变化订阅的Consumer。
const MyContext = React.createContext(defaultValue)
createContext接受一个参数defaultValue,如果Consumer上一级一直没有Provider,则会应用defaultValue作为value。只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效。
我们来模拟一个 Context.Provider和Context.Consumer的例子:
function ComponentB(){
/* 用 Consumer 订阅, 来自 Provider 中 value 的改变 */
return <MyContext.Consumer>
{
(value) => <ComponentA {
...value} /> }
</MyContext.Consumer>
}
function ComponentA(props){
const {
name , mes } = props
return <div>
<div> 姓名: {
name } </div>
<div> 想对大家说: {
mes } </div>
</div>
}
function index(){
const [ value , ] = React.useState({
name:'alien',
mes:'let us learn React '
})
return <div style={
{
marginTop:'50px' }} >
<MyContext.Provider value={
value} >
<ComponentB />
</MyContext.Provider>
</div>
}
打印结果:

Provider和Consumer的良好的特性,可以做数据的存和取,Consumer一方面传递value,另一方面可以订阅value的改变。
Provider还有一个特性可以层层传递value,这种特性在react-redux中表现的淋漓尽致。
createFactory
React.createFactory(type)
返回用于生成指定类型 React 元素的函数。类型参数既可以是标签名字符串(像是 'div' 或 'span'),也可以是 React 组件 类型 ( class 组件或函数组件),或是 React fragment 类型。
使用:
const Text = React.createFactory(()=><div>hello,world</div>)
function Index(){
return <div style={
{
marginTop:'50px' }} >
<Text/>
</div>
}
效果

报出警告,这个api将要被废弃,我们这里就不多讲了,如果想要达到同样的效果,请用React.createElement
createRef
createRef可以创建一个 ref 元素,附加在react元素上。
用法:
class Index extends React.Component{
constructor(props){
super(props)
this.node = React.createRef()
}
componentDidMount(){
console.log(this.node)
}
render(){
return <div ref={
this.node} > my name is alien </div>
}
}
个人觉得createRef这个方法,很鸡肋,我们完全可以class类组件中这么写,来捕获ref。
class Index extends React.Component{
node = null
componentDidMount(){
console.log(this.node)
}
render(){
return <div ref={
(node)=> this.node } > my name is alien </div>
}
}
或者在function组件中这么写:
function Index(){
const node = React.useRef(null)
useEffect(()=>{
console.log(node.current)
},[])
return <div ref={
node} > my name is alien </div>
}
isValidElement
这个方法可以用来检测是否为react element元素,接受待验证对象,返回true或者false。这个api可能对于业务组件的开发,作用不大,因为对于组件内部状态,都是已知的,我们根本就不需要去验证,是否是react element 元素。
但是,对于一起公共组件或是开源库,isValidElement就很有作用了。
实践
我们做一个场景,验证容器组件的所有子组件,过滤到非react element类型。
没有用isValidElement验证之前:
const Text = () => <div>hello,world</div>
class WarpComponent extends React.Component{
constructor(props){
super(props)
}
render(){
return this.props.children
}
}
function Index(){
return <div style={
{
marginTop:'50px' }} >
<WarpComponent>
<Text/>
<div> my name is alien </div>
Let's learn react together!
</WarpComponent>
</div>
}
过滤之前的效果

我们用isValidElement进行react element验证:
class WarpComponent extends React.Component{
constructor(props){
super(props)
this.newChidren = this.props.children.filter(item => React.isValidElement(item) )
}
render(){
return this.newChidren
}
}
过滤之后效果

过滤掉了非react element 的 Let's learn react together!。
Children.map
接下来的五个api都是和react.Chidren相关的,我们来分别介绍一下,我们先来看看官网的描述,React.Children 提供了用于处理 this.props.children 不透明数据结构的实用方法。
有的同学会问遍历 children用数组方法,map ,forEach 不就可以了吗? 请我们注意一下不透明数据结构,什么叫做不透明结构?
我们先看一下透明的结构:
class Text extends React.Component{
render(){
return <div>hello,world</div>
}
}
function WarpComponent(props){
console.log(props.children)
return props.children
}
function Index(){
return <div style={
{
marginTop:'50px' }} >
<WarpComponent>
<Text/>
<Text/>
<Text/>
<span>hello,world</span>
</WarpComponent>
</div>
}
打印

但是我们把Index结构改变一下:
function Index(){
return <div style={
{
marginTop:'50px' }} >
<WarpComponent>
{
new Array(3).fill(0).map(()=><Text/>) }
<span>hello,world</span>
</WarpComponent>
</div>
}
打印

这个数据结构,我们不能正常的遍历了,即使遍历也不能遍历,每一个子元素。此时就需要 react.Chidren 来帮忙了。
但是我们把WarpComponent组件用react.Chidren处理children:
function WarpComponent(props){
const newChildren = React.Children.map(props.children,(item)=>item)
console.log(newChildren)
return newChildren
}
此时就能正常遍历了,达到了预期效果。

注意
如果 children 是一个 Fragment 对象,它将被视为单一子节点的情况处理,而不会被遍历。
Children.forEach
Children.forEach和Children.map 用法类似,Children.map可以返回新的数组,Children.forEach仅停留在遍历阶段。
我们将上面的WarpComponent方法,用Children.forEach改一下。
function WarpComponent(props){
React.Children.forEach(props.children,(item)=>console.log(item))
return props.children
}
Children.count
children 中的组件总数量,等同于通过 map 或 forEach 调用回调函数的次数。对于更复杂的结果,Children.count可以返回同一级别子组件的数量。
我们还是把上述例子进行改造:
function WarpComponent(props){
const childrenCount = React.Children.count(props.children)
console.log(childrenCount,'childrenCount')
return props.children
}
function Index(){
return <div style={
{
marginTop:'50px' }} >
<WarpComponent>
{
new Array(3).fill(0).map((item,index) => new Array(2).fill(1).map((item,index1)=><Text key={
index+index1} />)) }
<span>hello,world</span>
</WarpComponent>
</div>
}
效果:

Children.toArray
Children.toArray返回,props.children扁平化后结果。
function WarpComponent(props){
const newChidrenArray = React.Children.toArray(props.children)
console.log(newChidrenArray,'newChidrenArray')
return newChidrenArray
}
function Index(){
return <div style={
{
marginTop:'50px' }} >
<WarpComponent>
{
new Array(3).fill(0).map((item,index)=>new Array(2).fill(1).map((item,index1)=><Text key={
index+index1} />)) }
<span>hello,world</span>
</WarpComponent>
</div>
}
效果:

newChidrenArray ,就是扁平化的数组结构。React.Children.toArray() 在拉平展开子节点列表时,更改 key 值以保留嵌套数组的语义。也就是说, toArray 会为返回数组中的每个 key 添加前缀,以使得每个元素 key 的范围都限定在此函数入参数组的对象内。
Children.only
验证 children 是否只有一个子节点(一个 React 元素),如果有则返回它,否则此方法会抛出错误。
不唯一
function WarpComponent(props){
console.log(React.Children.only(props.children))
return props.children
}
function Index(){
return <div style={
{
marginTop:'50px' }} >
<WarpComponent>
{
new Array(3).fill(0).map((item,index)=><Text key={
index} />) }
<span>hello,world</span>
</WarpComponent>
</div>
}
效果

唯一
function WarpComponent(props){
console.log(React.Children.only(props.children))
return props.children
}
function Index(){
return <div style={
{
marginTop:'50px' }} >
<WarpComponent>
<Text/>
</WarpComponent>
</div>
}
效果

React.Children.only() 不接受 React.Children.map() 的返回值,因为它是一个数组而并不是 React 元素。