十二、深入理解JSX

来源:互联网 发布:packageinfo.java作用 编辑:程序博客网 时间:2024/05/19 11:49

十二、深入理解JSX

从根本上讲,JSX就是提供了一个React.createElement(component, props, ...children)函数的语法糖。就像下面的JSX代码:

<MyButton color="blue" shadow={2}>    Click Me</MyButton>

经过编译后为:

React.createElement(    MyButton,    {color: 'blue', shadow: 2},    'Click Me')

如果一个标签没有子元素的话,你可以使用/>来自动闭合。例如:

<div className="sidebar" />

经过编译后为:

React.createElement(    'div',    {className: 'sidebar'},    null)

如果你想测试一些特定的JSX是如何转换成JavaScript的话,你可以试试在线Babel编译器

指定React元素类型

JSX标记的第一部分决定了React元素的类型。

首字母大写的类型表示JSX标记指的为React组件。 这些标签被编译为对指定变量的直接引用,因此如果使用JSX <Foo />表达式,Foo必须在当前的作用域内。

React必须在作用域内

由于JSX编译的本质是对React.createElement的调用,因此React库也必须始终在JSX代码的作用域中。
例如,虽然CustomButton没有直接引用React,但是这两个导入的模块在这段代码中也还是很有必要的:

import React from 'react';import ReactDOM from 'react-dom';function WarningButton(props) {    // return React.createElement(CustomButton, {color: 'red'}, null);    return <CustomButton color="red" />}

如果不使用JavaScript打包工具并将React通过script标签引入,那么它就会作为一个全局变量React

对JSX类型使用『点』表示符

您还可以使用JSX中的点表示符来引用React组件。 如果您有一个模块会导出很多React组件的话,使用这种方法就会十分方便。 例如,如果MyComponents.DatePicker是一个组件,您可以直接从JSX使用它:

import React from 'react';import ReactDOM from 'react-dom';const MyComponents = {    DatePicker(props) {        return <div>这里有一个颜色为{props.color}的日期选择器</div>    }};function BlueDataPicker(props) {    return <MyComponents.DatePicker color="blue" />}ReactDOM.render(    <BlueDataPicker />,    document.getElementById('root'));

用户自定义组件必须是首字母大写

当元素类型以是小写字母开头时,它指向一个内置组件,如<div><span>,并生成一个字符串'div''span'传递给React.createElement。 以大写字母开头的类型,如<Foo />编译为React.createElement(Foo),并且在当前作用域内寻找这个名称为Foo的已定义或已导入组件。

我们建议使用首字母大写命名组件。 如果你有一个以小写字母开头的组件,请在JSX中使用它之前请将它赋值给一个首字母大写的变量。

下面代码不会按预期运行:

import React from 'react';//这是错误的,这个组件应该为首字母大写function hello(props) {    // 这是正确的,因为div是一个有效的html标签    return <div>Hello {props.name}</div>;}function HelloWorld(props) {    // 这是错误的,因为它是首字母小写,所以React认为<hello />是一个html标签    return <hello name="zhangyatao" />}

想要修复上面的问题,我们必须将hello重命名为Hello,通过<Hello />来使用该组件:

import React from 'react';// 这是正确的function Hello(props) {    return <div>Hello {props.name}</div>;}function HelloWorld(props) {    // 这是正确的    return <Hello name="zhangyatao" />;}

在运行的时候选择组件类型

不能将常规的javascript表达式用作React元素类型。 如果你想使用一个通用表达式来表示元素的类型,只需将它赋值给一个首字母大写的变量即可。 这通常出现在当你想基于同一个props渲染一个不同的组件的情况下:

import React from 'react';import {Com1, Com2} from './Components';const components = {    myCom1: Com1,    myCom2: Com2}function RunCom(props) {    // 这是错误的,JSX的类型不能这么写    return <components[props.comType] type={props.type} />;}

想要解决上面的问题,只需要将它们赋值给一个首字母大写的变量即可:

import React from 'react';import {Com1, Com2} from './Components';const components = {    myCom1: Com1,    myCom2: Com2}function RunCom(props) {    // 这是正确的,将它们赋值给一个首字母大写的变量    const MyCom = components[props.comType];    return <MyCom type={props.type} />;}

JSX中的Props

在JSX中指定Props有以下几种不同的方法。

JavaScript表达式

你可以传递任何JavaScript表达式作为Props,用{}括住它们就可以使用。 例如,在这个JSX中:

<MyComponents foo={1 + 2 + 3 + 4} />

对于MyComponent来说,props.foo的值将为10,因为是通过表达式1 + 2 + 3 + 4计算得到的。

if语句和for循环在JavaScript中不是表达式,因此它们不能在JSX中直接使用。 相反,写完它们之后你可以把JSX放在里面。 例如:

function NumberDescriber(props) {    let description;    if (props.number % 2 === 0) {        description = <strong>偶数</strong>    } else {        description = <strong>奇数</strong>    }    return <div>{props.number}是一个{description}.</div>;}

字符串直接量

你可以传递一个字符串内容作为props。 这两个JSX表达式是等价的:

<MyComponent message="hi zhangyatao" /><MyComponent message={'hi zhangyatao'} />

当你传递一个字符串直接量时,它的值是经过html转义的。 所以这两个JSX表达式是等价的:

<MyComponent message='&lt;3' /><MyComponent message={'<3'} />

Props默认值为true

如果你没有给Props传入一个值,那么它的默认值为true,这两个JSX表达式是等价的:

<MyTextBox autocomplete /><MyTextBox autocomplete={true} />

一般来说,我们不建议使用它,因为它可以使用ES6对象的简写{foo},也就是{foo:foo}的简称会和{foo:true}混淆。 这种行为在这里只是方便它匹配到HTML行为。

Props传递

如果你有一个对象类似的数据作为props,并且想在JSX中传递它,你可以使用...作为一个“spread”运算符传递整个props对象。 这两个组件是等效的:

function App() {    return <Greeting firstName="yatao" lastName="zhang" />;}function App() {    const props = {firstName: 'yatao', lastName: 'zhang'};    return <Greeting {...props} />;}

当创建一个通用容器时,spread props很有用。 
然而,他们也可以让你的代码变得有点凌乱,这样很容易使大量不相关的prps传递给那些不关心它们的组件。 建议您谨慎使用此语法。

JSX中的子元素和子组件

在包含开始标记和结束标记的JSX表达式中,这些标记之间的内容通过一种特殊的prop:props.children传递。 有几种不同的方式传递子组件:

字符串直接量

你可以在开始和结束标签之间放一个字符串,那么props.children就是那个字符串。 这对许多内置的HTML元素很有用。 例如:

function MyComponent(props) {    return <div>{props.children}<div>; //=> <div>hello zhangyatao</div>}<MyComponent>Hello zhangyatao</MyComponent>

这是有效的JSX,并且MyComponent中的props.children将是字符串“Hello zhangyatao”。 HTML标签是不会经过转义的,所以你一般可以写JSX就像你写HTML一样:

<div>这是一个html标签 &amp; 同时也是个JSX</div>

JSX会删除行的开始和结尾处的空格。 它也会删除中间的空行。 与标签相邻的空行被会被删除;在字符串文本中间出现的空行会缩合成一个空格。 所以这些都渲染相同的事情:

<div>hello zhangyatao</div><div>    hello zhangyatao</div><div>    hello    zhangyatao</div><div>hello zhangyatao</div>

JSX子元素

你可以使用很多个JSX元素作为子元素。 这对需要嵌套的显示类型组件很有用:

<Dialog>    <DialogHeader />    <DialogBody />    <DialogFooter /></Dialog>

你可以将不同类型的子元素混合在一起,因此JSX子元素可以与字符串直接量一起使用。 这是JSX的另一种方式,就像一个HTML一样:

<div>    这是一个列表    <ul>        <li>item 1</li>        <li>item 2</li>    </ul></div>

一个React组件不可能返回多个React元素,但是一个JSX表达式可以包含多个子元素,因此如果你想让一个组件渲染多个东西,你可以将它们统一放置在就像上面那样的div中。

Javascript表达式

您可以将任何JavaScript表达式放在{}中作为子组件传递。 例如,下面这些表达式是等价的:

function MyComponent(props) {    return <div>{props.children}<div>; //=> <div>hi zhangyatao</div>}<MyComponent>hi zhangyatao</MyComponent><MyComponent>{'hi zhangyatao'}</MyComponent>

这通常用于渲染任意长度的JSX表达式列表。 例如,这将渲染一个HTML列表:

function Item(props) {    return <li>{props.message}</li>;}function TodoList(props) {    const todos = ['完成文档', '出去逛街', '打一局dota'];    return (        <ul>            {todos.map(message => <Item key={message} message={message} />)}        </ul>    );}

JavaScript表达式可以与其他类型的子元素混合使用。 这通常用于替换字符串模板:

function Hello(props) {    return <div>Hello {props.name}</div>;}

使用函数作为子元素

通常,插入JSX中的JavaScript表达式都最终返回为一个字符串、React元素、一个列表。

当然,props.children可以像任何其他props那样工作,它可以传递任何类型的数据,并不局限于那些告诉React应该如何渲染的东东。 例如,如果您有一个自定义组件,您可以将props.children作为一个回调函数:

import React from 'react';import ReactDOM from 'react-dom';function Repeat(props) {    let items = [];    let callback = props.children;    var numTimes = props.numTimes;    for(var i = 0 ; i < numTimes ; i++ ){        items.push(callback(i));    }    return <div>{items}</div>;}function ListOfTenThings(props) {    return (        <Repeat numTimes={10}>            {index => <div key={index}>这是列表中的第{index}项</div>}        </Repeat>    );}ReactDOM.render(    <ListOfTenThings/>,    document.getElementById('root'));

传递给自定义组件的子元素可以是任何东西,只要在React在渲染之前,该组件将它们转换为可以理解的东西即可。 这种用法并不常见,如果你想扩展JSX的其他能力,可以通过这个例子了解下它的工作原理。

布尔值、null、undefined在渲染时会被自动忽略

falsenullundefinedtrue是有效的子元素,不过他们从根本上讲是不参与渲染的。 这些JSX表达式将渲染处相同的东西:

<div /><div></div><div>{false}</div><div>{null}</div><div>{true}</div>

这对于有条件地呈现React元素很有用。 如果showHeadertrue,那么这个JSX只渲染一个<Header />

<div>    {showHeader && <Header />}    <Content /></div>

如果返回一些“假的”值就会收到一个警告,如数字0,不过React仍然会渲染。 例如,此代码将不会像您预期的那样工作,因为当props.messages是空数组时将打印0

<div>    {props.messages.length && <Message messages={props.messages} />}</div>

想要修复上面的问题,你要确定这个表达式在&&之前总返回布尔值:

<div>    {props.messages.length > 0 && <Message messages={props.messages} />}</div>

相反,如果你想要一个值如falsetruenullundefined出现在输出中,你必须先将它转换为字符串

import React from 'react';import ReactDOM from 'react-dom';function MyVariable(props) {    const myVariable = false;    // 如果这里不把false转换为字符串,这只会输出『我的javascript变量是』    const convertedVar = String(myVariable);    return (        <div>            我的javascript变量是{convertedVar}        </div>    );}ReactDOM.render(    <MyVariable/>,    document.getElementById('root'));
原创粉丝点击