name | kind | author | date |
---|---|---|---|
对React children 的深入理解 | react | lianpf | 2018-05-29 |
react的核心是组件, 使得你可以利用jsx像嵌套HTML标签一样去使用这些组件。
然而在包含jsx的表达式中, props.children
一直贯穿其中。
当你开始使用React时, 你一定认为props.children
也就那么回事, 但是你错了
1. Child components
子组件
父组件
包含了几个子组件
这三个 Component
组件都成为了 MyContainer
的 props.children 。
使用一个表达式容器,父组件就能够渲染它们的子组件:
class MyContainer extends React.Component {
render() {
return {this.props.children}
}
}
当然, 父组件可以决定不渲染任何的子组件或者在渲染之前对它们进行操作
// 不管你将什么子组件传递给这个组件,它都只会显示“Hello world!”
class OnlySayHellow extends React.Component {
render() {
return Hello world!
}
}
2.child 可以是任何东西
React 中的 Children不一定是组件,它们可以是任何东西
JSX表达式、JavaScript Expressions、Functions、Booleans, Null, and Undefined
JSX表达式
// component + string
Here is a component:
Here is another component:
注意 JSX将会自动删除每行开头和结尾的空格,以及空行。它还会把字符串中间的空白行压缩为一个空格
// 如下代码结果一致
// code 1
say hellow!
// code 2
say hellow!
// code 3
say
hellow!
// code 4
say hellow!
JavaScript Expressions
function Item(props) {
return {props.message} ;
}
function TodoList() {
const todos = ['finish doc', 'submit pr', 'nag dan to review'];
return (
{todos.map((message) => )}
);
}
Functions
例如,如果你有一个自定义组件,你可以把它当作回调来做。
// Calls the children callback numTimes to produce a repeated component
function Repeat(props) {
let items = [];
for (let i = 0; i < props.numTimes; i++) {
items.push(props.children(i));
}
return {items};
}
function ListOfTenThings() {
return (
{(index) => This is item {index} in the list}
);
}
Children passed to a custom component can be anything, as long as that
component transforms them into something React can understand before rendering.
This usage is not common, but it works if you want to stretch what JSX is capable of.
传递给自定义组件的孩子可以是任何东西,即使是function, 只要组件可以将它们通过某种方式呈现出来。但是, 这种用法并不常见
Booleans, Null, and Undefined Are Ignored
false, null, undefined, and true are valid children. They simply don’t render.
false, null, undefined, 和 true都是有效的子代,他们只是不渲染
// bad code
{props.messages.length &&
}
// good code
{props.messages.length > 0 &&
}
One caveat is that some “falsy” values, such as the 0 number, are still
rendered by React. For example, this code will not behave as you might
expect because 0 will be printed when props.messages is an empty array.
To fix this, make sure that the expression before && is always bool
**************************************** 分割线 ****************************************
为了处理 this.props.children 这个封闭的数据结构, React提供了一个顶层的API.
3. React.Children.map
循环
不仅仅在props.children为数组的情况下能起作用,除此之外,当函数、对象或者任何东西作为children传递时,也会起作用
object React.Children.map(object children, function fn [, object context])
使用方法:
React.Children.map(this.props.children, function (child) {
return {child} ;
})
其他方法
this.props.children.forEach(function (child) {
return {child}
})
注意
this.props.children
的值有三种可能:如果当前组件没有子节点,它就是 undefined ;
如果有一个子节点,数据类型是 object ;如果有多个子节点,数据类型就是 array 。
所以,处理this.props.children
的时候要小心
demo
class IgnoreFirstChild extends React.Component {
render() {
const children = this.props.children
return (
{React.Children.map(children, (child, i) => {
// Ignore the first child
if (i < 1) return
return child
})}
)
}
}
First
Second
// <- Only this is rendered
在这种情况下,我们也可以使用 this.props.children.map 的方法。但是假如
this.props.children
是一个函数而不是一个数组,接着我们就会产生一个error!
this.props.children.map is not a function
React 提供一个工具方法 React.Children 来处理 this.props.children 。我们可以用
React.Children.map 来遍历子节点,而不用担心 this.props.children 的数据类型是
undefined 还是 object, 甚至是function. 使用 React.Children.map 函数,无论什么都不会报错
4. React.Children.forEach
循环 类似于 React.Children.map(), 但是不返回对象
React.Children.forEach(object children, function fn [, object context])
5. React.Children.count
计数 因为this.props.children 可以是任何类型的,检查一个组件有多少个children是非常困难的
object React.Children.only(object children)
不要天真的以为, 可以使用this.props.children.length
, 当传递的是字符串或者
函数时程序中断可能会让你哭!
假设我们有个child:
"say hello!"
,但是使用.length
的方法将会显示为10。
class ChildrenCounter extends React.Component {
render() {
console.log(React.Children.count(this.props.children));
return {this.props.children}
}
}
{() => First!}
Second!
Third!
注意
注意: 此处console结果可能为2或者3, 有的地方会报错
Functions are not valid as a React child
,
此时count为2, 若不报错, 则为3
6. React.Children.toArray
转换为数组 可以通过 React.Children.toArray 方法将children转换为数组, 如果你
需要对它们进行排序,这个方法是非常有用的
class Sort extends React.Component {
render() {
const children = React.Children.toArray(this.props.children)
// Sort and render the children
return {children.sort().join(' ')}
}
}
// We use expression containers to make sure our strings
// are passed as three children, not as one string
{'bananas'}{'oranges'}{'apples'}
// 渲染为三个排好序的字符串
apples bananas oranges
7. React.Children.only
执行单一child
object React.Children.only(object children)
返回 children 中 仅有的子级, 否则抛出异常。这里仅有的子级, only方法接受的参数只能
是一个对象,不能是多个对象(数组)
class Executioner extends React.Component {
render() {
return React.Children.only(this.props.children)()
}
}
这样只会返回一个child。如果不止一个child,它就会抛出错误,让整个程序陷入中断——完美
的避开了试图破坏组件的懒惰的开发者
8. renderChildren
改变children的属性
class RadioGroup extends React.Component {
constructor() {
super()
// Bind the method to the component context
this.renderChildren = this.renderChildren.bind(this)
}
renderChildren() {
return React.Children.map(this.props.children, child => {
// TODO: Change the name prop to this.props.name
return child
})
}
render() {
return (
{this.renderChildren()}
)
}
}
9. React.cloneElement
永恒地克隆元素
给每一个child增加一个name属性
renderChildren() {
return React.Children.map(this.props.children, child => {
return React.cloneElement(child, {
name: this.props.name
})
})
}
这个场景就是RadioGroup的实现原理
First
Second
传递一个唯一的 name 给RadioGroup, 相当于给其内每一个radio增加name属性, 属于同一组
参考
1.react官方文档
2.A deep dive into children in React