react 16学习(三)
April 04, 2018/ Edit on Github ✏️文章目录
ReactDom 在 16 版本中也新增了一些新的功能,比如createPortal,hydrate。今天主要学习一下createPortal。 先看下 ReactDom 的大致包含的一些属性和方法。
const ReactDom = {
createPortal, //reacte16中新增的
hydrate, //reacte16中新增的
findDomNode,
render,
unmountComponentAtNode,
flushSync,
unstable_renderSubtreeIntoContainer,
unstable_createPortal, //这个实际就是现在的createPortal,将在React17版本中移除
unstable_batchedUpdates,
unstable_deferredUpdates,
unstable_flushControlled,
}
可以看到我们熟悉的findDomNode
,render
,unmountComponentAtNode
。这里面新增了两个新的方法createPortal
和hydrate
。以 unstable_开头的表示的是当前版本中存在的方法,但可能会在后续版本中改动或移除,所以不建议使用。
createPortal
先来看下这个方法的签名:
export function createPortal(
children: ReactNodeList,
container: DOMContainer,
key: ?string = null,
): ReactPortal {
return {
// This tag allow us to uniquely identify this as a React Portal
$$typeof: REACT_PORTAL_TYPE,
key: key == null ? null : '' + key,
children,
containerInfo,
implementation:null,
};
}
可以看到它接受三个参数,第一个 children,要渲染的节点元素,第二个是 container,要渲染的容器,第三个是可选的参数 key,这个参数一般没用,默认为 null。 它的作用就是在任意合法的 dom 节点都可以用作渲染的容器,而不仅仅是当前父级节点。下面为官方的说明:
Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.
通常情况下,我们在render
中挂载 React child 到它最近的父节点下,例如
render() {
// React mounts a new div and renders the children into it
return (
<div>
{this.props.children}
</div>
);
}
而createPortal却可以使得 React child 挂载到任何合法的 dom 节点下
render() {
// React does *not* create a new div. It renders the children into `domNode`.
// `domNode` is any valid DOM node, regardless of its location in the DOM.
return ReactDOM.createPortal(
this.props.children,
domNode,
);
}
虽然createPortal可以挂载到任何合法的 dom 容器中,但是它在 React tree 中的层次结构是不会受影响的,比如事件冒泡。在用createPortal渲染的组件中触发一个事件,依然可以被它在 React tree 中的父级节点捕获到。看下面的例子: 例如有下面的 html 结构
<html>
<body>
<div id="app-root"></div>
<div id="modal-root"></div>
</body>
</html>
在 React tree 中,app-root 节点依然可以捕获到 modal-root 节点冒泡出来的事件。
// These two containers are siblings in the DOM
const appRoot = document.getElementById("app-root")
const modalRoot = document.getElementById("modal-root")
class Modal extends React.Component {
constructor(props) {
super(props)
this.el = document.createElement("div")
}
componentDidMount() {
// The portal element is inserted in the DOM tree after
// the Modal's children are mounted, meaning that children
// will be mounted on a detached DOM node. If a child
// component requires to be attached to the DOM tree
// immediately when mounted, for example to measure a
// DOM node, or uses 'autoFocus' in a descendant, add
// state to Modal and only render the children when Modal
// is inserted in the DOM tree.
modalRoot.appendChild(this.el)
}
componentWillUnmount() {
modalRoot.removeChild(this.el)
}
render() {
return ReactDOM.createPortal(this.props.children, this.el)
}
}
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = { clicks: 0 }
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
// This will fire when the button in Child is clicked,
// updating Parent's state, even though button
// is not direct descendant in the DOM.
this.setState(prevState => ({
clicks: prevState.clicks + 1,
}))
}
render() {
return (
<div onClick={this.handleClick}>
<p>Number of clicks: {this.state.clicks}</p>
<p>
Open up the browser DevTools to observe that the button is not a child
of the div with the onClick handler.
</p>
<Modal>
<Child />
</Modal>
</div>
)
}
}
function Child() {
// The click event on this button will bubble up to parent,
// because there is no 'onClick' attribute defined
return (
<div className="modal">
<button>Click</button>
</div>
)
}
ReactDOM.render(<Parent />, appRoot)
若有收获,小额鼓励