工具类
# 工具类
# createElement
一提到createElement,就不由得和JSX联系一起。我们写的jsx,最终会被 babel,用createElement编译成react元素形式。
下面看个实例:
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");
}
createElement模型:
React.createElement(
type,
[props],
[...children]
)
createElement参数:
第一个参数:如果是组件类型,会传入组件,如果是dom元素类型,传入div或者span之类的字符串。
第二个参数:第二个参数为一个对象,在dom类型中为属性,在组件类型中为props。
其他参数:依次为children,根据顺序排列。
createElement做了些什么?
经过createElement处理,最终会形成 $$typeof = Symbol(react.element)对象。对象上保存了该react.element的信息。
# cloneElement
createElement把我们写的jsx,变成element对象; 而cloneElement的作用是以 element 元素为样板克隆并返回新的 React 元素。返回元素的 props 是将新的 props 与原始元素的 props 浅层合并后的结果。
那么cloneElement感觉在我们实际业务组件中,可能没什么用,但是在一些开源项目,或者是公共插槽组件中用处还是比较大的,比如说,我们可以在组件中,劫持children element,然后通过cloneElement克隆element,混入props。经典的案例就是 react-router中的Swtich组件,通过这种方式,来匹配唯一的 Route并加以渲染。
举个例子:
function FatherComponent({ children }){
const newChildren = React.cloneElement(children, { age: 18})
return <div> { newChildren } </div>
}
function SonComponent(props){
console.log(props) // {name: 'alien', age: 18}
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就很有作用了。
# Children.map
React.Children 提供了用于处理 this.props.children 不透明数据结构的实用方法。
我们先看一下透明的结构:
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 来帮忙了。
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仅停留在遍历阶段。
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>
}
报错。