Clicke Me </MyButton>会编译为: React...">
最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 深入浅出 React -- JSX

    正文概述 掘金(lyn-ho)   2020-12-07   417

    什么是 JSX

    如下 JSX 代码:

    <MyButton color="blue" shadowSize={2}>
    	Clicke Me
    </MyButton>
    

    会编译为:

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

    React 必须在作用域内

    由于 JSX 会编译为 React.createElement 调用形式,所以 React 库也必须包含在 JSX 代码作用域内

    如果不使用打包工具而是直接通过 <script> 标签加载 React,则必须将 React 挂载到全局变量中

    用户自定义的组件必须以大写字母开头

    以小写字母开头的元素代表一个 HTML 内置组件,比如 <div> 或者 <span> 会生成相应的字符串 'div' 或者 'span' 传递给 React.createElement(作为参数)。大写字母开头的元素则对应着在 JavaScript 引入或自定义的组件,如 <Foo /> 会编译为 React.createElement(Foo)

    JSX 语法

    在 JSX 中嵌入表达式

    在 JSX 语法中,你可以在打括号内放置任何有效的 JavaScript 表达式

    function formatName(user) {
      return user.firstName + ' ' + user.lastName;
    }
    
    const user = {
      firstName: 'Harper',
      lastName: 'Perez'
    }
    
    const element = (
      <h1>
        Hello, {formatName(user)}!
      </h1>
    )
    
    ReactDOM.render(
      element,
      document.getElementById('root')
    )
    

    JSX 也是一个表达式

    在编译后,JSX 表达式会被转为普通 JavaScript 函数调用,并且对其取值后得到 JavaScript 对象

    function getGreeting(user) {
      if (user) {
        return <h1>Hello, {formatName(user)}!</h1>;
      }
      return <h1>Hello, Stranger.</h1>;
    }
    
    

    JSX 特定属性

    在属性中嵌入 JavaScript 表达式时,不要在大括号外面加上引号。你应该仅使用引号(对于字符串值)或大括号(对于表达式)中的一个,对于同一属性不能同时使用这两种符号。

    const element = <img src={user.avatarUrl}></img>
    

    使用 JSX 指定子元素

    假如一个标签里面没有内容,你可以使用 /> 来闭合标签,就像 XML 语法一样:

    const element = <img src={user.avatarUrl} />
    

    JSX 标签里能够包含很多子元素:

    const element = (
      <div>
        <h1>Hello!</h1>
        <h2>Good to see you here.</h2>
      </div>
    )
    

    JSX 防止注入攻击

    React DOM 在渲染所有输入内容之前,默认会进行转义。

    所有的内容在渲染之前都被转换成了字符串。这样可以有效地防止 XSS(cross-site-scripting, 跨站脚本)攻击。

    JSX 表示对象

    Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用

    以下两种示例代码完全等效:

    const element = (
      <h1 className="greeting">
        Hello, world!
      </h1>
    )
    
    const element = React.createElement(
      'h1',
      {className: 'greeting'},
      'Hello, world!'
    )
    

    JSX 中的 Props

    JavaScript 表达式作为 Props

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

    字符串字面量

    以下两个 JSX 表达式是等价的:

    <MyComponent message="hello world" />
    
    <MyComponent message={'hello world'} />
    

    当你将字符串字面量赋值给 prop 时,它的值是未转义的

    以下两个 JSX 表达式是等价的:

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

    Props 默认值为 true

    以下两个 JSX 表达式是等价的:

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

    属性展开

    可以使用展开运算符 ... 来在 JSX 中传递整个 props 对象。

    以下两个组件是等价的:

    function App1() {
      return <Greeting firstName="Ben" lastName="Hector" />;
    }
    
    function App2() {
      const props = {firstName: 'Ben', lastName: 'Hector'};
      return <Greeting {...props} />;
    }
    

    JSX 中的子元素

    包含在开始和结束标签之间的 JSX 表达式内容将作为特定属性 props.children 传递给外层组件。

    有几种不同的方法来传递子元素:

    字符串字面量

    <MyComponent>Hello world!</MyComponent>
    

    编译为:

    <div>This is valid HTML &amp; JSX at the same time.</div>
    

    JSX 会移除行首尾的空格以及空行。与标签相邻的空行均会被删除,文本字符串之间的新行会被压缩为一个空格。

    因此以下的几种方式都是等价的:

    <div>Hello World</div>
    
    <div>
      Hello World
    </div>
    
    <div>
      Hello
      World
    </div>
    
    <div>
    
      Hello World
    </div>
    

    JSX 子元素

    <MyContainer>
      <MyFirstComponent />
      <MySecondComponent />
    </MyContainer>
    

    JavaScript 表达式作为子元素

    JavaScript 表达式可以被包裹在 {} 中作为子元素。

    以下表达式是等价的:

    <MyComponent>foo</MyComponent>
    
    <MyComponent>{'foo'}</MyComponent>
    

    这对于展示任意长度的列表非常有用。例如,渲染 HTML 列表:

    function Item(props) {
      return <li>{props.message}</li>;
    }
    
    function TodoList() {
      const todos = ['finish doc', 'submit pr', 'nag dan to review'];
      return (
        <ul>
          {todos.map((message) => <Item key={message} message={message} />)}
        </ul>
      );
    }
    

    函数作为子元素

    你可以将任何东西作为子元素传递给自定义组件,只要确保在该组件渲染之前能够被转换成 React 理解的对象。这种用法并不常见,但可以用于扩展 JSX。

    function Item(props) {
      return <li>{props.message}</li>;
    }
    
    function TodoList() {
      const todos = ['finish doc', 'submit pr', 'nag dan to review'];
      return (
        <ul>
          {todos.map((message) => <Item key={message} message={message} />)}
        </ul>
      );
    }
    

    布尔类型、Null 以及 Undefined 将会忽略

    false, null, undefined, and true 是合法的子元素。但它们并不会被渲染。

    以下的 JSX 表达式渲染结果相同:

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

    这有助于依据特定条件来渲染其他的 React 元素。例如,在以下 JSX 中,仅当 showHeadertrue 时,才会渲染 <Header /> 组件:

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

    值得注意的是有一些 “falsy” 值,如数字 0,仍然会被 React 渲染。例如,以下代码并不会像你预期那样工作,因为当 props.messages 是空数组时,0 仍然会被渲染:

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

    要解决这个问题,确保 && 之前的表达式总是布尔值:

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

    反之,如果你想渲染 falsetruenullundefined 等值,你需要先将它们转换为字符串:

    <div>
      My JavaScript variable is {String(myVariable)}.
    </div>
    

    JSX 的本质:JavaScript 的语法扩展

    React 官网给出的一段定义:

    那么 “JSX 语法时如何在 JavaScript 中生效的

    JSX 语法是如何在 JavaScript 中生效的:认识 Babel

    JSX 定位是 JavaScript 的“扩展”,这就直接决定了浏览器不会天然支持 JSX。那么,JSX 的语法是如何在 JavaScript 中生效的呢?React 官网给出了答案:

    “编译” 这个动作,是由 Babel 来完成的

    什么是Babel

    例如箭头函数:

    // Babel 输入: ES2015 箭头函数
    [1, 2, 3].map((n) => n + 1);
    
    // Babel 输出: ES5 语法实现的同等功能
    [1, 2, 3].map(function(n) {
      return n + 1;
    });
    

    类似的,Babel 也具备将 JSX 语法转换为 JavaScript 代码的能力

    看看开头的示例:

    深入浅出 React -- JSX

    我们写的 JSX 其实写的就React.createElement,虽然它看起来有点像 HTML,但也只是“看起来像”而已。JSX 的本质是 React.createElement 这个 JavaScript 调用的语法糖,这也呼应了 React 官方给出的“JSX 充分具备 JavaScript 的能力”这句话。

    为什么选择 JSX

    既然 JSX 等价于一次 React.createElement 调用,为什么不直接使用 React.createElement 来创建元素呢?

    原因很简单,在效果一致的前提下,JSX 代码层次分明、嵌套关系清晰,而 React.createElement 代码混乱杂糅,不仅难以阅读,也难以编码

    JSX 语法糖允许前端开发者使用我们最为熟悉的类 HTML 标签语法来创建虚拟 DOM,在降低学习成本的同时,也提升了研发效率与研发体验。

    React 官网也提出:

    JSX 是如何映射为 DOM 的:阅读 createElement 源码

    我们先大致理解 createElement 函数源码的作用:

    //注意:react只写了3个参数,实际上,从第三个参数往后都是children
    export function createElement(type, config, children) {
      let propName; // 用于存储后面需要用到的元素属性
    
      // Reserved names are extracted
      const props = {}; // 用于存储元素属性的键值对集合
    
      let key = null;
      let ref = null;
      let self = null;
      let source = null;
      // 赋给标签的props不为空时
      // config 存储元素的属性
      if (config != null) {
        // 依次对 ref、key、self 和 source 属性赋值
        if (hasValidRef(config)) {
          ref = config.ref;
        }
        if (hasValidKey(config)) {
          // 防止是Number
          key = '' + config.key;
        }
        //__self、__source 暂时不知道是干啥用的属性
        self = config.__self === undefined ? null : config.__self;
        source = config.__source === undefined ? null : config.__source;
        // Remaining properties are added to a new props object
        for (propName in config) {
          // 如果config中的属性不是标签原生属性,则放入props对象中
          if (
    
            hasOwnProperty.call(config, propName) &&
            !RESERVED_PROPS.hasOwnProperty(propName)
          ) {
            props[propName] = config[propName];
          }
        }
      }
    
      // Children can be more than one argument, and those are transferred onto
      // the newly allocated props object.
      // 子元素数量
      const childrenLength = arguments.length - 2;
      if (childrenLength === 1) {
        props.children = children;
      } else if (childrenLength > 1) {
        const childArray = Array(childrenLength);
        // 依次将children push进array中
        for (let i = 0; i < childrenLength; i++) {
          childArray[i] = arguments[i + 2];
        }
        
        // 开发中写的this.props.children就是子元素的集合
        props.children = childArray;
      }
    
      // Resolve default props
    
      // 为传入的props设置默认值,比如:
      //class Comp extends React.Component{
      //  static defaultProps = {
      //     aaa: 'one',
      //     bbb: () => {},
      //     ccc: {},
      //   };
      //
      // }
    
      if (type && type.defaultProps) {
        const defaultProps = type.defaultProps;
        for (propName in defaultProps) {
          // 如果props数组中未设值,则设置默认值(注意:null也算设置了值)
          if (props[propName] === undefined) {
            props[propName] = defaultProps[propName];
          }
        }
      }
    
      return ReactElement(
        type,  //'div'
        key,  //null
        ref,  //null
        self, //null
        source, //null
        ReactCurrentOwner.current, // null或Fiber
        props, // 自定义的属性、方法,注意:props.children=childArray
      );
    }
    

    参数:创建一个元素需要哪些信息

    export function createElement(type, config, children)
    
    • type:用于表示节点的类型。它可以是标准 HTML 标签字符串,也可以是 React 组件类型或 React Fragment 类型
    • config:以对象形式传入,组件所有的属性都会以键值对的形式存储在 config 对象中
    • children:以对象形式传入,它记录的是组件标签之间嵌套的内容,也就是所谓的 “子节点” “子元素”

    下面的示例可以帮助增进对 createElement 的理解:

    React.createElement('div', {
      className: 'wrapper'
    }, React.createElement('h1', {
      className: 'header'
    }, 'header'), React.createElement('p', {
      className: 'content'
    }, 'content'))
    

    对应的 DOM 结构:

    <div class="wrapper">
    	<h1 class="header">header</h1>
      <p class="content">content</p>
    </div>
    

    createElement 分析

    逻辑流程图

    深入浅出 React -- JSX

    createElement 中并没有复杂的逻辑,它的每一步步骤几乎都是在格式化数据

    createElement 就如同是开发者和 ReactElement 调用之间的一个 “转换器”,对数据进行处理

    返回值:初识虚拟 DOM

    createElement 执行到最后会 return 一个 ReactElement 的调用

    下面是关于 ReactElement 的源码及解析:

    const ReactElement = function(type, key, ref, self, source, owner, props) {
      const element = {
        // REACT_ELEMENT_TYPE是一个常量,用来标识该对象是一个ReactElement
        $$typeof: REACT_ELEMENT_TYPE,
    
        // 内置属性赋值
        type: type,
        key: key,
        ref: ref,
        props: props,
    
        // 记录创造该元素的组件
        _owner: owner,
      };
    
      if (__DEV__) {
      }
    
      return element;
    };
    
    

    ReactElement 只做了一件事,那就是创建ReactElement 把传入的参数按照一定的规范,“组装”进 element 对象,并将它返回给 React.createElement,最终 React.createElement 又将它返回到开发者

    对于 ReactElement 对象实例,其本质上是以 JavaScript 对象形式存在的对 DOM 的描述,也就是 “虚拟 DOM”(更准确地说,是虚拟 DOM 中的一个节点)

    从虚拟 DOM 到真实 DOM 需要调用 ReactDOM.render 方法

    在每个 React 项目的入口文件中,都有对 React.render 函数的调用。下面是 ReactDOM.render 的函数签名:

    ReactDOM.render(
    	// 需要渲染的元素(ReactElement)
      element,
      
      // 元素挂载的目标容器(真实 DOM)
      container,
      
      // 回调函数,用于处理渲染结束后的逻辑。可选
      [callback]
    )
    

    总结

    JSX 经过 babel 转换为 React.createElement 函数,再调用 React.createElementReactElement 返回一个 element 对象(虚拟 DOM),最后通过 React.render 函数的调用,生产真实 DOM 节点并挂载到 “容器” 上。


    起源地下载网 » 深入浅出 React -- JSX

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元