Fullstack React 学习笔记(四)

JSX and the Virtual DOM

React 和许多其他的 JavaScript 前端框架不同,它并不会直接操作浏览器的 DOM,取而代之的是其内部构建了一个虚拟 DOM,用户也只和这个虚拟 DOM 打交道,React 会将虚拟 DOM 的修改同步到浏览器 DOM 上。

不直接操作浏览器 DOM,而需要虚拟 DOM 的理由是:

  • 很难追踪哪里被修改过
  • 响应速度会变缓慢

什么是虚拟 DOM?

虚拟 DOM 是一颗 JavaScript 对象树(对应着实际 DOM),当使用虚拟 DOM 时,我们的代码在每次更新时都要重新创建整个 DOM。

开发者只需要构建自己想要的 DOM 即可,剩下的与实际 DOM 交互等幕后工作交给 React 就行了。每次更新都要重新构建虚拟 DOM 看似很慢,但 React 的虚拟 DOM 其实已经优化地相当快了,它能做到:

  • 使用高效的 diff 算法,找出改变的地方
  • 同时更新 DOM 的子树
  • 批量更新 DOM

我们的工作就是提供给 React 足够的信息来构建 JavaScript 对象,以便于下一步渲染。React 的虚拟 DOM 可以看做是一棵由 ReactElements 组成的树结构。

Virtual DOM 和 Shadow DOM 并不是同一种概念

ReactElement

在 Virtual DOM 中,ReactElement 是一种 DOM 元素的表现形式,学习最好的方式还是在浏览器中试验一把,打开示例文件

此时只有一个 <div id='root' /> 元素,现在我们想添加一个 标签(在 DOM 中),当然之前提到过,不会直接去实际 DOM 里创建。

相反,React 期望我们提供一棵虚拟 DOM 树,即为 React 创建一组 JavaScript 对象,Reeact 会自己转换成实际的 DOM 树。

这棵虚拟 DOM 节点树的组成对象是 ReactElements,下面我们来创建 ReactElement 对象

var boldElement = React.createElement('b');

Rendering Our ReactElement

现在虚拟 DOM 已经添加了 boldElement,为了让其显示在浏览器上,我们需要渲染这个元素到实际的 DOM 树中。这里使用了 ReactDOM.render() 方法,它接收两个参数:

  • The root of our virtual tree
  • 该组件想要添加的位置

获取元素位置的方式有很多:

// Either of these will work
var mountElement = document.getElementById('root'); var mountElement = document.querySelector('#root');
// if we were using jQuery this would work too
var mountElement = $('#root')

有了具体的位置,就能让 React 插入渲染好的组件了

var boldElement = React.createElement('b');
var mountElement = document.querySelector('#root'); 
// Render the boldElement in the DOM tree 
ReactDOM.render(boldElement, mountElement);

Adding Text (with children)

我们想要在标签 间添加点文本,这涉及到创建元素的子元素,之前的 React.createElement() 方法只携带了一个参数(b 标签),其实它能接收三个参数:

  • DOM 元素的类型
  • 元素的 props
  • 元素的子元素

稍后介绍 props,DOM 元素的子元素必须是一个 ReactNode 对象,它可以是以下三种类型之一:

  • ReactElement
  • 一个字符串或数字(一个 ReactText 对象)
  • 一个 ReactNodes 数组

例如我们要在标签 <b> </b> 之间添加子元素(一段字符串),可以向 createElement() 方法第三个参数传递一段字符串

var mountElement = document.querySelector('#root');
// Third argument is the inner text
var boldElement = React.createElement('b', null, "Text (as a string)"); ReactDOM.render(boldElement, mountElement);

ReactDOM.render()

React 会将虚拟 DOM 树渲染到浏览器的 DOM 树上,但是 React 的渲染不仅仅能渲染到浏览器上,它还可以渲染到多种类型的『画布』上,甚至可以渲染到其他框架视图上,例如移动 APP 的视图,在 React Native 会详细提到这一点。

const component = ReactDOM.render(boldElement, mountElement);

我们可以多次调用 ReactDOM.render(),但它仅执行必要的更新到 DOM 上。此外 ReactDOM.render() 还接收第三个参数:「一个回调函数」它在组件渲染完成/更新后执行

ReactDOM.render(boldElement, mountElement, function() { 
    // The React app has been rendered/updated
});

JSX

JSX Creates Elements

HTML 非常适合多层嵌套 tag 的结构,为此 React 提供了类似的语法 JSX,能够让我们像写 HTML 一样写 JS:

// 传统的写法
var boldElement = React.createElement('b', null, "Text (as a string)");
// JSX 的写法
var boldElement = <b>Text (as a string)</b>;

JSX 允许我们创建自己 tags(用来封装其他组件的功能),HTML tag 以小写字母打头,而 React 组件的 tag 以大写字母打头

1  // html tag
2  const htmlElement = (<div>Hello world</div>);
3
4  // React component
5  const Message = props => (<div>{props.text}</div>)
6
7  // Use our React component with a `Message` tag
8  const reactComponent = (<Message text="Hello world" />);

JSX 在载入浏览器之前,会使用预处理构建工具对其进行转换成 JavaScript,这个工具通常是 babel。

JSX 除了长得像 HTML 之外,它还支持与 JavaScript 混编。下面我们从更加结构化的角度来剖析下 JSX。

JSX Attribute Expressions

为了在 React 组件的属性中使用 JavaScript 表达式,我们使用了大括号包裹 { }

// ...
const warningLevel = 'debug'; 
const component = (<Alert
                                    color={warningLevel === 'debug' ? 'gray' : 'red'} 
                                    log={true} />)

JSX Conditional Child Expressions

根据条件渲染具体的子组件

// ...
const renderAdminMenu = function() {
    return (<MenuLink to="/users">User accounts</MenuLink>)
}
// ...
const userLevel = this.props.userLevel; 
return (
  <ul>
    <li>Menu</li>
    {userLevel === 'admin' && renderAdminMenu()}
</ul> )

‘||’ 在 javascript 中的规则:如果 || 左侧表达式的值为真值,则返回左侧表达式的值;否则返回右侧表达式的值。
&&’ 在 javascript 中的规则:如果 && 左侧表达式的值为真值,则返回右侧表达式的值;否则返回左侧表达式的值

我们也可以在 JSX 中使用三目运算符

const Menu = (<ul>{loggedInUser ? <UserMenu /> : <LoginLink />}</ul>)

JSX Boolean Attributes

在 HTML 中,有些属性需要设置一个 BOOL 值,例如一个禁用的输入框 元素定义为:

<input name='Name' disabled />

在 React 中,我们只需直接作为属性传入一个 true 或 false 即可:

// Set the boolean in brackets directly
<input name='Name' disabled={true} />

// ... or use JavaScript variables
let formDisabled = true;
<input name='Name' disabled={formDisabled} />

JSX 的注释

let userLevel = 'admin'; 
{/*
    Show the admin menu if the userLevel is 'admin' 
*/}
{userLevel === 'admin' && <AdminMenu />}

JSX Spread Syntax

有时候,你需要向子组件传递很多 props,例如你有一个 props 对象包含两个 key

const props = {msg: "Hello", recipient: "World"}

可以像这样单独传递每个 prop:

<Component msg={"Hello"} recipient={"World"} />

但通过 JSX spread syntax 可以这样做:

<Component {...props} />
<!-- essentially the same as this: -->
<Component msg={"Hello"} recipient={"World"} />

JSX 要点

尽管 JSX 类似于 HTML,但二者还有很多不同,以下要点务必牢记于心

要点一:class 和 className

因为 JSX 绑定的 JavaScript,因此我们不能在 tag 中使用 JS 的一些保留字符(例如 for 和 class),否则会引发冲突

1  <!-- Same as <div class='box'></div> --> 
2  <div className='box'></div>

属性 className 类似于 HTML 中的 class 属性,它们都是接收一个字符串,来标识出相关的 CSS 类

在 JSX 传递多个 class,我们可以 join 一个数组转然后换为字符串

var cssNames = ['box', 'alert']
// and use the array of cssNames in JSX 
(<div className={cssNames.join(' ')}></div>)
要点二:通过 classnames 管理 className

classnames 是一个很赞的扩展,用来管理 css classes,它可以获取字符串或对象的列表,并允许我们有条件地将类应用到元素中。

classnames 包接收参数,转换成对象,然后有条件地应用一个 CSS 类

class App extends React.Component { 
    render() {
        const klasses = classnames({
            box: true,   // always apply the box class
            alert: this.props.isAlert,   // if the prop is set 
            severity: this.state.onHighAlert,   // with state 
            timed: false   // never apply this class
        });
        return React.createElement(
            'div',
            {className: klasses},
            React.createElement('h1', {}, 'Hello world')
        ); 
    }
}
要点三:for 和 htmlFor

同样的原因,在 JSX 中不能使用属性 for,而要使用 htmlFor 替代

<!-- ... -->
<label htmlFor='email'>Email</label> 
<input name='email' type='email' /> 
<!-- ... -->
要点四:HTML 实体和表情符号 Emoji

Entities(实体)是 HTML 中保留的字符,包含小于号 <、大于号 > 、版权符号等;在 HTML 中直接使用 entity code 来显示:

<ul>
  <li>phone: &phone;</li>
  <li>star: &star;</li>
</ul>

而在 JSX 中,我们需要包裹一层大括号来显示 { },此外使用 unicode 也是支持的

return ( <ul>
    <li>phone: {'\u0260e'}</li>
    <li>star: {'\u2606'}</li>
  </ul>
)

Emoji 也不在话下:

return( <ul>
    <li>dolphin: {'\uD83D\uDC2C'}</li>
    <li>dolphin: {'\uD83D\uDC2C'}</li>
    <li>dolphin: {'\uD83D\uDC2C'}</li>
</ul> )

要点五:data-anything

如果我们想要应用一些 HTML 规范不包括的属性,则可以使用在属性上加 data- 前缀

<div className='box' data-dismissible={true} />
<span data-highlight={true} />

这个要求只适用于原生对应 HTML 的 DOM 组件,并不是说自定义的组件就不能接收任意字符作为 props 了。在自定义的组件上,是可以接受任意字符属性的。

<Message dismissible={true} />
<Note highlight={true} />
JSX 总结

JSX 并不是魔法,它其实是 React.createElement 方法的语法糖,JSX 将会解析我们所写的 tags,然后创建 JavaScript 对象。JSX 是一种很方便的语法,来帮助我们构建组件树。

前面说过,通过 JSX 我们得到了一个 ReactElement

var boldElement = <b>Text (as a string)</b>; 
// => boldElement is now a ReactElement

然后将 ReactElement 传递给 ReactDOM.render 去渲染。

不过,现在还存在一个问题: ReactElement 是无状态的且不可变的,如果想要添加一些交互性(通过修改 state),那就需要另一个块拼图 ReactElement 了,下一章再深入研究下 ReactElement


-EOF-

walkingway

Read more posts by this author.

Subscribe to Talk is cheap, Show me the world!

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!