《深入 React 技术栈》章节试读

前端外刊评论 2016-12-01 00:59

《深入 React 技术栈》尽管并不是一本外文译作,但 React 与 Redux 方面的经验都是从国外的著作、博客与文档中汲取,结合对它们的理解与实践沉淀在'...

《深入 React 技术栈》尽管并不是一本外文译作,但 React 与 Redux 方面的经验都是从国外的著作、博客与文档中汲取,结合对它们的理解与实践沉淀在 pure render 专栏上,所谓知识无国界,前端外刊评论亦是如此。@寸志兄在本书写作期间参与了试读,并是本书的推荐人之一,在此非常感谢。 — 陈屹 @流形

现各大网络书店均已上架,在此放出第一章的原文一节试读,还请读者们不吝指正。

1.6 React 与 DOM

前面已经介绍完组件的组成部分了,但还缺少最后一环,那就是将组件渲染到真实 DOM 上。从 React 0.14 版本开始,React 将 React 中涉及 DOM 操作的部分剥离开,目的是为了抽象 React,同时适用于 Web 端和移动端。ReactDOM 的关注点在 DOM 上,因此只适用于 Web 端。

在 React 组件的开发实现中,我们并不会用到 ReactDOM,只有在顶层组件以及由于 React 模型所限而不得不操作 DOM 的时候,才会使用它。

1.6.1 ReactDOM

ReactDOM 中的 API 非常少,只有 `findDOMNode`、`unmountComponentAtNode` 和 `render`,我们就以 API 的角度来讲讲它们的用法。

findDOMNode

上一节我们已经讲过组件的生命周期,DOM 真正被添加到 HTML 中的生命周期方法是 componentDidMount 和 componentDidUpdate 方法。在这两个方法中,我们可以获取真正的 DOM 元素。React 提供的获取 DOM 元素的方法有两种,其中一种就是 ReactDOM 提供的 `findDOMNode`。

DOMElement findDOMNode(ReactComponent component)

当组件被渲染到 DOM 中后,findDOMNode 返回该 React 组件实例相应的 DOM 节点。它可以被用于获取表单的 value 以及 DOM 的测量上。例如,假设要在当前组件加载完时获取当前 DOM,就可以使用 findDOMNode:

import React, { Component } from 'react';

class App extends Component { componentDidMount() { // this 为当前组件的实例 const dom = findDOMNode(this); } render() {}}

如果在 render 中返回 null,那么 findDOMNode 也返回 null。findDOMNode 只对已经挂载的组件有效。

涉及到复杂操作时,还有非常多的原生 DOM API 可以用。但是需要严格限制场景,在使用之前多问自己为什么要操作 DOM。

render

为什么说只有在顶层组件我们不得不使用 ReactDOM,因为我们要把 React 渲染的 Virtual DOM 渲染到浏览器的 DOM 当中,这就要使用 render 方法了。

ReactComponent render( ReactElement element, DOMElement container, [function callback])

render 方法把元素挂载到 container 中,并且返回 element 的实例(即 ref 引用)。当然,如果是无状态组件,render 会返回 null。当组件装载完毕时,callback 就会被调用。

当组件在初次渲染之后再次更新时,React 不会把整个组件重新渲染一次,而会用它高效的 DOM diff 算法做局部的更新。这也是 React 最大的亮点之一!

此外,与 render 相反,React 还提供了一个很少使用 unmountComponentAtNode 方法来做 unMount 的操作。

1.6.2 ReactDOM 不稳定方法

ReactDOM 中有两个不稳定方法,其中一个方法与 render 方法颇为相似。讲起它,还得从我们常用的 Dialog 组件在 React 中的实现讲起。

我们先来回忆一下 Dialog 组件的特点,它是不在文档流中的弹出框,一般会绝对定位在屏幕的正中央,背后有一层半透明的遮罩。因此,它往往直接渲染在 document.body 下,然而我们并不知道如何在 React 组件外进行操作。这就要从实现 Dialog 的思路以及涉及 DOM 部分的实现讲起。

这里我们引入 Portal 组件,这是一个经典的实现,最初的实现来源于 React Bootstrap 组件库中的 Overlay Mixin,后来使用越来越广泛。我们截取关键部分的源码:

import React from 'react';

import ReactDOM, { findDOMNode } from 'react-dom';

import CSSPropertyOperations from 'react/lib/CSSPropertyOperations';

export default class Portal extends React.Component { constructor() { // ... } openPortal(props = this.props) { this.setState({ active: true }); this.renderPortal(props); this.props.onOpen(this.node); } closePortal(isUnmounted = false) { const resetPortalState = () => { if (this.node) { ReactDOM.unmountComponentAtNode(this.node); document.body.removeChild(this.node); } this.portal = null; this.node = null; if (isUnmounted !== true) { this.setState({ active: false }); } }; if (this.state.active) { if (this.props.beforeClose) { this.props.beforeClose(this.node, resetPortalState); } else { resetPortalState(); } this.props.onClose(); } }renderPortal(props) { if (!this.node) { this.node = document.('div'); // 在节点增加到 DOM 之前,执行 CSS 防止无效的重绘 this.applyClassNameAndStyle(props); document.body.(this.node); } else { // 当新的 props 传下来的时候,更新 CSS this.applyClassNameAndStyle(props); } let children = props.children; // https://gist.github.com/jimfb/d99e0678e9da715ccf6454961ef04d1b if (typeof props.children.type === 'function') { children = React.cloneElement(props.children, { closePortal: this.closePortal }); } this.portal = ReactDOM.unstable_renderSubtreeIntoContainer( this, children, this.node, this.props.onUpdate ); }render() { if (this.props.openByClickOn) { return React.cloneElement(this.props.openByClickOn, { onClick: this.handleWrapperClick }); } return null; }}

从 Portal 组件看得出,我们实现了一个『壳』,包括触发事件,渲染的位置以及暴露的方法,它并不关心子组件的内容。当我们使用它的时候,可以这么写。

<Portal ref="myPortal"> <Modal title="My modal"> Modal content </Modal></Portal>

这个组件可以说是 Dialog 实现的精髓,我们 Dialog 的行为抽象了 Portal 这个父组件。

当我们调用上述代码时,可以注意到在 componentDidMount 的时候最后调用了 this.renderPortal() 方法,这个方法把 children 里的内容插入到 document.body 下,这就实现 children 不在标准文档流的渲染。

这之间就说到了 ReactDOM 中不稳定的 API 方法 unstable_renderSubtreeIntoContainer。它的作用很简单,就是更新组件到传入的 DOM 节点上,我们在这里使用它完成了在组件内实现跨组件的 DOM 操作。

这个方法与 render 是不是很相似,但 render 方法缺少了一个插件某一个节点的参数。从最终 ReactDOM 方法实现的源代码 react/src/renderers/dom/client/ReactMount.js 中了解 unstable_renderSubtreeIntoContainer 与 render 方法对应调用的方法区别是:

render: ReactMount._renderSubtreeIntoContainer(null, nextElement, container, callback);

unstable_renderSubtreeIntoContainer: ReactMount._renderSubtreeIntoContainer(parentComponent, nextElement, container, callback);

源代码证明了我们的猜想,也就说明两者区别在于是否传入父节点。

另一个不稳定方法 unstable_renderSubtreeIntoContainer 关于 setState 的更新策略,我们会在『$3.4 解密 setState』中详细介绍。

1.6.3 refs

刚才我们已经详述了 ReactDOM 的 render 方法,比如我们渲染了一个 App 组件到 root节点下了:

const myAppInstance = ReactDOM.render(<App />, document.getElementById('root'));

myAppInstance.doSth();

我们利用 render 方法拿到了 App 组件的实例,然后就可以对它做一些操作。但在组件内,JSX 是不会返回一个组件的实例的!它只是一个 ReactElement,只是告诉 React 被挂载的组件应该长什么样。

前端外刊评论还发布了更多
最新文章
猜你喜欢