文章名称来自豆瓣高分电影列表, 电影名为《这个杀手不太冷》
前言
我们假设你有一定的前端知识,简单使用过React但不了解他的原理以及为什么要用它。
通过这篇文章你会了解到这些问题的答案:
为什么你自己写的组件要首字母大写?
为什么能在js文件里写html的语法?
为什么jsx的方式要比模板强?
为什么jsx必须要有一个顶层节点?
为什么jsx中不能用class要用className?
为什么react-dom要单独作为一个库?
为什么我即使没在这个js中用到React也需要引入React这个库?
看文章之前请记住:
在js中的所有东西都是js。
什么是jsx?
首先上一段我们熟悉的jsx代码, 这种在js中写类似html代码的方式就是jsx。
<MyButton color="blue" shadowSize={2}>
Click Me
</MyButton>
其实jsx是React.createElement(component, props, …children)
函数的语法糖。这段代码编译之后就成了:
React.createElement(
MyButton,
{color: 'blue', shadowSize: 2},
'Click Me'
)
那么为什么是这三个参数呢?
仔细观察一下熟悉的DOM代码
<div class='box' id='content'>
<div class='title'>Hello</div>
<button>Click</button>
</div>
想一下如何用 JavaScript 对象来表现一个 DOM 元素的结构
{
tag: 'div',
attrs: { className: 'box', id: 'content'},
children: [
{
tag: 'div',
arrts: { className: 'title' },
children: ['Hello']
},
{
tag: 'button',
attrs: null,
children: ['Click']
}
]
}
你会发现每个DOM结构都可以通过DOM名称, DOM属性,DOM子元素来表示,DOM一层套一层形成了DOM树。
那么为什么不直接在js中写UI呢?
你比较下html方式的UI表示和js方式的UI表示的代码行数你就知道了:
太长
不清晰
所以React用jsx语法让我们能在js中可以用HTML的方式描述UI。但这坨代码肯定不是标准的js是吧,直接运行肯定是不行的,所以需要编译。其实就是树的递归调用啊…想想你写斐波那契数列程序的时候画的图。
<div>
<h1 className='title'>React 小书</h1>
</div>
=>
React.createElement(
"div",
null,
React.createElement(
"h1",
{ className: 'title' },
"React 小书"
)
)
)
那么生成的代码怎么变成真的DOM?
光是生成了一堆函数调用并不是真正的DOM,它只是包含了生成一个DOM树所需要的所有信息,我们称它为VDOM(虚拟DOM)。那么用什么东西把它变成真正的DOM呢? 想一下你每次是怎么引入React的?
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
class Header extends Component {
...
}
ReactDOM.render(
<Header />,
document.getElementById('root')
)
除了基本的React库,用于扩展的Component组件基类, 这个react-dom
是什么? ReactDOM用于渲染组件并构造DOM树,然后插入到页面上的某个挂载点上。
那么为什么不把这玩意和react库封装在一起?
因为这个UI信息并不一定要渲染到网页上,比如渲染到Canvas上,渲染到手机上。
渲染到canvas上的叫react-canvas
渲染到APP上的叫ReactNative
总结下过程:
// JSX -> VDOM: 将jsx编译成vdom
let vdom = <div id="foo">Hello!</div>;
// VDOM -> DOM: 将vdom渲染成dom
let dom = render(vdom);
// add the tree to <body>: 将dom插入到挂载点
document.body.appendChild(dom);
那么为什么非要一层VDOM呢?
因为原生的DOM操作非常费时, 和 DOM 操作比起来,js 计算是极其便宜,有了Vdom之后我们可以直接在这个js对象上操作,而不用直接与DOM打交道。这样的话可以减少浏览器的重排,极大的优化性能。React会在每次数据变化之后更新DOM,但不是更新真实的DOM,而是存在内存中的JS对象,不管你数据怎么变化,React都可以以最小的代价来更新 DOM。
虚拟DOM是React的一个非常重要的概念,不同的类React框架中对虚拟DOM的实现有差异,造成了其性能的千差万别。VDOM比较复杂,这期不介绍。
等等,我还有几个个小问题
为什么我即使没在这个js中用到React也需要引入React这个库?
答: 还记得在文章开头说过的在js中的所有东西都是js吗? 只要你在这个js文件中用到了jsx语法,那么这个jsx会被翻译成React.createElement()的形式,你看如果你不引入React库,这段代码能执行吗?不对啊,有时候我不需要引入这两个库也能用React,怎么解释?
答: 那是因为你把react和react-dom放在html文件的标签中了, React成为全局变量为什么React的组件名一定要大写?
答: 因为普通的html标签都是小写的, div, a, p,那么React如何区分是已有的HTML标签还是用户自定义的组件呢?就是首字母大小写, 如果你小写你的组件名称,react会把它当原生html标签,然后报错因为找不到为什么组件必须要有一个顶层节点?
答:React15以下组件需要包一个顶层节点,否则会报Adjacent XJS elements must be wrapped in an enclosing tag
的错,为什么呢? 再复习一遍在js当中所有东西都是js
,并列的两个tag 会渲染成什么样子?React.createElement(...) React.createElement(...)
并不符合语法,但如果做成数组形式返回其实是可以的,因此React16中终于支持了返回数组的写法。这个问题的issue14年就已提出来了,有兴趣的同学可以研究一下。
render() {
return [
<li key="A">First item</li>,
<li key="B">Second item</li>,
<li key="C">Third item</li>,
];
}
为什么要用jsx?
说了那么多,我还是觉得jquery还有vm模板好用,那么我为什么要迁移到React + jsx中来呢?
=> 因为一方面用jsx+React我们可以使用js的所有语法和能力,而使用模板引擎通常只能使用其提供的有限的模板语法。
举个栗子🌰,循环列表,在vm中我们只能用这样的语法写:
<ul>
#foreach ( $product in $allProducts )
<li> $product </li>
#end
</ul>
而在jsx中, 我们可以:
// 用map写
let list = items => items.map( p => <li> {p} </li> );
// 用循环写
let list = [];
for (let i = 0 ; i < items.length: i++) {
list.push(<li>{items[i]}</li>);
}
// 用forEach写
let list = [];
items.forEach(item => list.push(<li>{item}</li>))
// 用while写
// 用for...of写
// ...
总之你爱怎么写就怎么写。这极大地拓展了前端写界面的能力,前端同学心里美滋滋。
另一方面jsx结合React能发挥它前端组件化的优势,提高代码复用率,避免手动操作DOM等,这里不赘述。
等下你这些代码里的{}是什么? 为什么不直接在{}里写循环套jsx
这个是jsx提供给你插入表达式的,包括变量,表达式计算,函数调用都可以放在里面。如何判断是否是表达式? 看他可不可以放在等于号右边。
所以if, for这样的就不是表达式了,所以我们也不能在{}中写for循环和if判断,但我们可以把结果暂存到变量中,然后插入到{}中,或者用?表达式啊。
另外这个表达式不仅可以用在标签内部,还能用在标签的属性上,属性props是jsx的一个重要概念。
props?属性? 为什么需要属性
首先我们聊聊为什么要组件化,组件化是为了代码的复用和分治。比如你的同事写了一个红色的按钮组件,有了React,我只要引入一下就可以用到我的工程里了,太棒了。但是等等,我的工程里需要的是一个蓝色的按钮,难道我要去改组件的代码?我希望我可以传一个颜色参数进去改变组件的样式,而这个需求的实现方式就是props。你可以理解为函数的参数,传入不同的参数,输出不同的值,没有参数的函数功能太过限定了。
所以 props的作用是让外部能对组件自己进行配置。
<div className="sidebar" color="red"/>
注意props一旦传入到组件中,它就是只读的,不可以再赋值。如果 props 渲染过程中可以被修改,那么就会导致这个组件显示形态和行为变得不可预测,这样会可能会给组件使用者带来困惑
咦,这个className是什么鬼,为什么不用class?
让我们再来复习一下开篇的话: 在js中的所有东西都是js。当然不止class,同样的还有htmlFor替代for。
React.createElement(
MyButton,
{class: 'blue', shadowSize: 2},
'Click Me'
)
通常的说辞是class是js的保留字。不过翻译成js好像没什么问题? 即使class是js的保留词依然可以用在属性语法中才对,只是不能做变量标识。是的,就有类react框架preact直接用的class,详见issue,甚至还有自动用babel帮我们做转换的jsx-html-class
React中用className的原因参考quora,总结一下原因有两点:
我们在操作html属性的时候一般用的是
el.className=...
,而不是el.setAttribute('class', ...)
,Attributes一般赋值字符串,而属性名可以赋值对象,更灵活。所以jsx中的className和HMTL的className属性表现一致,没毛病未来react可能会用…来解构this.props,这时候class和for作为保留字不能作为变量标识,就不能适用这种情况了。
求带啊,有没有其他React技巧
来来来,新鲜的React技巧便宜卖,10元一条,请扫我的付款码…好吧,其实都是jsx-in-depth里的。
- 属性可以用字符串字面量, 会进行解码
<MyComponent message="<3" />
===
<MyComponent message={'<3'} />
- 如果你不给属性赋值,那么默认值为true。这和html中的语义是一样的,但不建议使用,还是下面的直观
<MyTextBox autocomplete />
===
<MyTextBox autocomplete={true} />
- 善用…,如果属性在对象中,解构对象轻松搞定
function App2() {
const props = {firstName: 'Ben', lastName: 'Hector'};
return <Greeting {...props} />;
}
- 布尔值,null,undefined作为子组件不会渲染,如果你想渲染这些值可以先转成string,如
{String(myVariable)}
。这个可以用来做条件渲染,注意showHeader得是一个boolean值,不能是0这样的伪false值。
<div>
{showHeader && <Header />}
<Content />
</div>
jsx深度剖析
to be continued 😃