JSX 书写规范
代码语言:javascript复制JSX 支持换行
let jsx = (
<div>
<h1>hello world</h1>
<button/>
</div>
)
- JSX的顶层只能有一个根元素,我们很多时候会在最外层包裹一个
div
(后续React推出了不占据Dom结构的Fragment
,同时,<></>
空标签有同样的效果,但是测试后发现这个在vue中不生效,) - 为了方便阅读,我们通常在jsx的外层包裹一个小括号
()
,这样可以方便阅读,并且jsx可以进行换行书写; - JSX中的标签可以使单标签,也可以是双标签
如果是单标签,必须以/>结尾
JSX 注释都要用花括号{}包起来
代码语言:javascript复制{
//我是单行注释
}
{/*我是一段注释*/}
JSX 插入变量
代码语言:javascript复制const t = 'hello world';
let jsx = (
<div>
<h1>{t}</h1>
<button/>
</div>
)
JSX 嵌入表达式
- 运算表达式
const arg1 = 1;
const arg2 = 2;
let jsx = (
<div>
<h1>{arg1 arg2}</h1>
<button/>
</div>
)
- 三元表达式
const t = 'hello world';
const arg1 = 1;
const arg2 = 2;
const hasButton = true;
let jsx = (
<div>
<h1>
{
t === 'hello world' ? arg1 : arg2
}
</h1>
{
//如果hasButton为true,则渲染button组件
hasButton && <button/>
}
</div>
)
- 函数调用
const t = 'hello world';
const arg1 = 1;
const arg2 = 2;
const hasButton = true;
const func1 = ()=>{ return (<div>func1</div>) }
let jsx = (
<div>
<h1>
{
//如果在render外定义的函数,请注意加this:this.func1()
func1()
}
</h1>
</div>
)
JSX 绑定属性
- 绑定普通属性Attrs
const title = 'hello world';
let jsx = (
<div>
<h1 title={title}>hello world</h1>
<button/>
</div>
)
- 绑定class
代码语言:javascript复制在jsx中,class属性需要指定为className,因为class在JavaScript中是保留字段
const hasCss = true;
const h1Css = [
'flex',
hasCss ? 'css' : 'noCss',
]
let jsx = (
<div>
<h1 className='flex'>hello world</h1>
<h1 className={h1Css}>hello world</h1>
<button/>
</div>
)
- 绑定style
代码语言:javascript复制在jsx中,windows风格的命名方式(属性、style、方法、event)都需要转换成驼峰式的写法,比如,正常写一个style指定左边的外边距:margin-left:‘10px’,
这里需要改成:marginLeft:‘10px’
const title = 'hello world';
let jsx = (
<div>
<h1 style={{ marginLeft:'10px',width:'100%' }}>hello world</h1>
<button/>
</div>
)
JSX 绑定事件
- JSX中绑定事件类似在HTML原生中绑定事件,只不过React中事件命名采用小驼峰(camelCase),而不是纯小写;
- 但是我们会发现在我们绑定的回调事件中访问我们对应的this会是undefined,这是因为对应的回调函数是React内部帮我们去进行调用的,React无法确定对应的this所以采用的是callback.apply(undefined,[])方式调用,改变了this的指向为undefined。[
这条规则不适用于Vue,因为在Vue中对this做了特殊处理
]
function func1(){
console.log(this); // undefined
}
render(){
let jsx = (
<div>
<button onClick={this.func1}/>
</div>
)
return jsx;
}
如果我们需要在事件中通过this
来访问React
组件本身属性和方法,有以下几条解决方案:
- 通过bind绑定this(显示绑定)
function func1(arg1, arg2, e){
console.log(this); // ReactCom
console.log(arg1); // param1
console.log(arg2); // param2
console.log(e); // Event from buttonClick
}
render(){
let jsx = (
<div>
<button onClick={this.func1.bind(this,'param1','param2')}/>
</div>
)
return jsx;
}
使用bind绑定的方式除了可以非常简单的获取到事件对象event之外,还可以传递我们想要传递的参数
- 除了显示绑定之外,我们可以使用匿名函数(箭头函数)的方式
function func1(arg1, arg2, e){
console.log(this); // ReactCom
console.log(arg1); // param1
console.log(arg2); // param2
console.log(e); // Event from buttonClick
}
render(){
let jsx = (
<div>
<button onClick={(e)=> {
this.func1('param1','param2', e);
}}/>
</div>
)
return jsx;
}
- 同理,在声明函数的时候我们使用箭头函数的方式也可以达到同样效果[
如果想要传递我们自己的参数,还是需要用到bind
]
const func1 = (e) => {
console.log(this); // ReactCom
console.log(e); // Event from buttonClick
}
render(){
let jsx = (
<div>
<button onClick={this.func1}/>
<button onClick={this.func1.bind(this,'param1')}/>
</div>
)
return jsx;
}
这里高阶的同学要注意!
如果是在JSX中使用事件绑定,请不要使用箭头函数的方式去声明方法甚至直接在JSX中使用箭头函数绑定事件。
因为根据VR的render渲染机制,如果使用箭头函数,那么每当组件的state发生改变,推动render渲染执行的时候,如果存在箭头函数,每次浏览器都会分配新的内存和额外的开销来执行事件的绑定,组件绑定的层级越深,额外开销越大。
所以,为了最优的性能考虑,请在constructor
构造函数中对需要绑定的事件做显示绑定
constructor() {
this.func1 = this.func1.bind(this);
}
function func1(e){
console.log(this); // ReactCom
console.log(e); // Event from buttonClick
}
render(){
let jsx = (
<div>
<button onClick={this.func1}/>
</div>
)
return jsx;
}
JSX 条件渲染
- 在
jsx
中,不允许使用if
、if-else
,请使用三元运算符
或者逻辑与&&
- 同样,也允许使用
for
循环,请使用JS中的高阶函数map
、filter
……
const t = 'hello world';
const arg1 = 1;
const arg2 = 2;
const hasButton = true;
const list = [1,2,3,4,5,6,7,8,9];
let jsx = (
<div>
<h1>
{
t === 'hello world' ? arg1 : arg2
}
</h1>
{
//如果hasButton为true,则渲染button组件
hasButton && <button/>
}
<ul>
{
list.map((item) => <li>{item}</li>)
}
</ul>
</div>
)
createElement
要更透彻的了解和学习JSX,浅析其本质,那么一定要先了解createElement
因为提到JSX
,不可避免的需要提到createElement
,所以,是不是奇奇怪怪的知识又增加了 : )
从React中看createElement
JSX实际上仅仅是React.createElement(type, config, children)方法的语法糖,该方法接收三个参数:
- type
- 当前ReactElement的类型,如果是标签元素,那么使用字符串表示“div”,如果是组件元素直接使用组件的名称就可以。
- config
- 我们在JSX中绑定的属性会在config对象中以键值对的形式存在。
- children
- 存放标签中的内容,以children数组的形式存储
我们都知道,JSX是通过babel
进行解析的,而我们编写JSX的时候必须依赖babel
代码语言:javascript复制我们可以再babel的官网查看JSX的转换过程:传送门
<!-- 转换示例代码 -->
<div>
<h1 className='flex'>hello world</h1>
<h1 style={{marginLeft:'10px'}}>hello world</h1>
<button/>
</div>
如果我们直接使用React.createElement()来编写代码,就不需要以来bable进行解析也可以正常的渲染显示
代码语言:javascript复制render(){
return 'use strict';
/*#__PURE__*/
React.createElement("div", null, /*#__PURE__*/React.createElement("h1", {
className: "flex"
}, "hello world"), /*#__PURE__*/React.createElement("h1", {
style: {
marginLeft: '10px'
}
}, "hello world"), /*#__PURE__*/React.createElement("button", null));
}
我们通过React.createElement()方法最后返回得到的是一个ReactElement
对象,
这个ReactElement
对象作用是什么?
其实React利用ReactElement
对象组成了一个JavaScript对象树,这个对象树就是我们经常讲的一个概念--虚拟DOM(VR DOM)
,我们可以将之前jsx返回的结果进行打印来查看对应的ReactElemnt对象:
render(){
const arg1 = 1;
const arg2 = 4;
let jsx = (
<div>
<div>{arg1 arg2}</div>
<div className="flex">
<button/>
</div>
<div className={{marginLeft:'10px'}}>hellow world</div>
</div>
)
console.log(jsx);
return jsx;
},
我们编写的JSX代码经过bable编译解析成对应的React.createElement()
方法形式,
经过React.createElement()
方法调用返回我们对应的ReactElement对象树
(虚拟DOM树),对应的ReactElement对象树
经过ReactDOM.render()
方法转换为真正的DOM在我们的浏览器进行渲染。
JSX -> VR DOM -> DOM
为什么要用VR DOM
- 很难跟踪状态发生的改变:原有的开发模式,我们很难跟踪到状态发生的改变,不方便针对我们应用程序进行调试;
- 操作真实DOM性能较低:传统的开发模式会进行频繁的DOM操作,而这一的做法性能非常的低;
- DOM操作非常耗费性能
- document.createElement本身创建出来的就是一个非常复杂的对象:传送门
- DOM操作会引起浏览器的回流和重绘,所以在开发中应该避免频繁的DOM操作
不是用了VR DOM性能就一定会变好
React 从来没有说过 “React 比原生操作 DOM 快”。
React 的基本思维模式是每次有变动就整个重新渲染整个应用。
如果没有 Virtual DOM,简单来想就是直接重置 innerHTML。
很多人都没有意识到,在一个大型列表所有数据都变了的情况下,重置 innerHTML 其实是一个还算合理的操作... 真正的问题是在 “全部重新渲染” 的思维模式下,即使只有一行数据变了,它也需要重置整个 innerHTML,这时候显然就有大量的浪费。
我们可以比较一下 innerHTML
vs. Virtual DOM
的重绘性能消耗:
- innerHTML: render html string O(template size) 重新创建所有 DOM 元素 O(DOM size)
- Virtual DOM: render Virtual DOM diff O(template size) 必要的 DOM 更新 O(DOM change)
Virtual DOM render diff 显然比渲染 html 字符串要慢。
但是!它依然是纯 js 层面的计算,比起后面的 DOM 操作来说,依然便宜了太多。
可以看到,innerHTML
的总计算量不管是 js 计算还是 DOM 操作都是和整个界面的大小相关,但 Virtual DOM
的计算量里面,只有 js 计算和界面大小相关,DOM 操作是和数据的变动量相关的。
前面说了,和 DOM
操作比起来,js
计算是极其便宜的。这才是为什么要有 Virtual DOM
:
它保证了:
- 不管你的数据变化多少,每次重绘的性能都可以接受;
- 你依然可以用类似
innerHTML
的思路去写你的应用。