内容简介:为了让大家深刻理解 JSX 的含义。有必要简单介绍了一下 JSX 稍微底层的运作原理,这样大家可以更加深刻理解 JSX 到底是什么东西,为什么要有这种语法,它是经过怎么样的转化变成页面的元素的。思考一个问题:如何用 JavaScript 对象来表现一个 DOM 元素的结构,举个例子:每个 DOM 元素的结构都可以用 JavaScript 的对象来表示。你会发现一个 DOM 元素包含的信息其实只有三个:标签名,属性,子元素。
为了让大家深刻理解 JSX 的含义。有必要简单介绍了一下 JSX 稍微底层的运作原理,这样大家可以更加深刻理解 JSX 到底是什么东西,为什么要有这种语法,它是经过怎么样的转化变成页面的元素的。
思考一个问题:如何用 JavaScript 对象来表现一个 DOM 元素的结构,举个例子:
<div class='box' id='content'> <div class='title'>Hello</div> <button>Click</button> </div> 复制代码
每个 DOM 元素的结构都可以用 JavaScript 的对象来表示。你会发现一个 DOM 元素包含的信息其实只有三个:标签名,属性,子元素。
所以其实上面这个 HTML 所有的信息我们都可以用合法的 JavaScript 对象来表示:
{ tag: 'div', attrs: { className: 'box', id: 'content'}, children: [ { tag: 'div', arrts: { className: 'title' }, children: ['Hello'] }, { tag: 'button', attrs: null, children: ['Click'] } ] } 复制代码
你会发现,HTML 的信息和 JavaScript 所包含的结构和信息其实是一样的,我们可以用 JavaScript 对象来描述所有能用 HTML 表示的 UI 信息。但是用 JavaScript 写起来太长了,结构看起来又不清晰,用 HTML 的方式写起来就方便很多了。
于是 React.js 就把 JavaScript 的语法扩展了一下,让 JavaScript 语言能够支持这种直接在 JavaScript 代码里面编写类似 HTML 标签结构的语法,这样写起来就方便很多了。编译的过程会把类似 HTML 的 JSX 结构转换成 JavaScript 的对象结构。
编译前代码
import React, { Component } from 'react' import ReactDOM from 'react-dom' import './index.css' class Header extends Component { render () { return ( <div> <h1 className='title'>React 小书</h1> </div> ) } } ReactDOM.render( <Header />, document.getElementById('root') ) 复制代码
经过编译以后会变成:
import React, { Component } from 'react' import ReactDOM from 'react-dom' import './index.css' class Header extends Component { render () { return ( React.createElement( "div", null, React.createElement( "h1", { className: 'title' }, "React 小书" ) ) ) } } ReactDOM.render( React.createElement(Header, null), document.getElementById('root') ); 复制代码
React.createElement 会构建一个 JavaScript 对象来描述你 HTML 结构的信息,包括标签名、属性、还有子元素等。这样的代码就是合法的 JavaScript 代码了。所以使用 React 和 JSX 的时候一定要经过编译的过程。
这里再重复一遍:所谓的 JSX 其实就是 JavaScript 对象。每当在 JavaScript 代码中看到这种 JSX 结构的时候,脑子里面就可以自动做转化,这样对你理解 React.js 的组件写法很有好处。
实现
注意到的是我们的JSX最终转化成为的是React.createElement这个方法:
第一个参数是字符串类型或者组件或者symbol,
代表的是标签元素, 如div, span classComponent或者是functional Component, 原生提供的Fragment, AsyncMode等, 会被特殊处理 复制代码
第二个参数是一个对象类型, 代表标签的属性, id, class
其余的参数代表的是children,不涉及grand-children,当子节点中有孙节点的时候, 再递归使用React.createElement方法
const App = () => { return <div id="app" key="key"> <section> <img /> </section> <span>span</span> </div> } "use strict"; var App = function App() { return React.createElement( "div", {id: "app",key: "key"}, React.createElement("section", null, React.createElement("img", null) ), React.createElement("span", null, "span")); }; 复制代码
当第一个参数是组件的时候,第一个参数是作为变量传入的, 可以想像的是, React.createElement内部有一个简单的判断, 如果传入的是组件的话, 内部还会调用React.createElement方法
const Child = () => { return <div>Child</div> } const App = () => { return <div id="app"> <Child /> </div> } "use strict"; var Child = function Child() { return React.createElement("div", null, "Child"); }; var App = function App() { return React.createElement( "div", {id: "app"}, React.createElement(Child, null)); //这里 } } 复制代码
需要注意的是如果组件开头是一个小写的话, 会被解析成简单的字符串,在运行的时候就会报错
我们的createElement方法定义在packages/src/ReactElement.js
export function createElement(type, config, children) { let propName; const props = {}; let key = null; let ref = null; let self = null; let source = null; // ref和key和其他props不同, 进行单独处理 if (config != null) { if (hasValidRef(config)) { ref = config.ref; } if (hasValidKey(config)) { key = '' + config.key; } self = config.__self === undefined ? null : config.__self; source = config.__source === undefined ? null : config.__source; //将属性名挂载到props上 for (propName in config) { if ( hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName) ) { props[propName] = config[propName]; } } } //第三个及以上的参数都被看作是子节点 const childrenLength = arguments.length - 2; if (childrenLength === 1) { props.children = children; } else if (childrenLength > 1) { //当数组长度确定时,这种方式比push要节省内存 const childArray = Array(childrenLength); for (let i = 0; i < childrenLength; i++) { childArray[i] = arguments[i + 2]; } props.children = childArray; // merge defaultProps if (type && type.defaultProps) { const defaultProps = type.defaultProps; for (propName in defaultProps) { if (props[propName] === undefined) { props[propName] = defaultProps[propName]; } } } return ReactElement( type, key, ref, self, source, ReactCurrentOwner.current, props, ); } 复制代码
ReactElement定义如下
const ReactElement = function(type, key, ref, self, source, owner, props) { const element = { // 每个react element的$$typeof属性都是REACT_ELEMENT_TYPE $$typeof: REACT_ELEMENT_TYPE, // react element的内置属性 type: type, key: key, ref: ref, props: props, _owner: owner, //创建该元素的component }; return element; }; 复制代码
总的来说就是返回一个react element, 该element带有props, refs, type
所以可以总结一下从 JSX 到页面到底经过了什么样的过程:
第一个原因是,当我们拿到一个表示 UI 的结构和信息的对象以后,不一定会把元素渲染到浏览器的普通页面上,我们有可能把这个结构渲染到 canvas 上,或者是手机 App 上。所以这也是为什么会要把 react-dom 单独抽离出来的原因,可以想象有一个叫 react-canvas 可以帮我们把 UI 渲染到 canvas 上,或者是有一个叫 react-app 可以帮我们把它转换成原生的 App(实际上这玩意叫 ReactNative)。
第二个原因是,有了这样一个对象。当数据变化,需要更新组件的时候,就可以用比较快的算法操作这个 JavaScript 对象,而不用直接操作页面上的 DOM,这样可以尽量少的减少浏览器重排,极大地优化性能。这个在以后的章节中我们会提到。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- ReactNative源码解析-初识源码
- Spring源码系列:BeanDefinition源码解析
- Spring源码分析:AOP源码解析(下篇)
- Spring源码分析:AOP源码解析(上篇)
- 注册中心 Eureka 源码解析 —— EndPoint 与 解析器
- 新一代Json解析库Moshi源码解析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。