Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

这个jsx不太冷 #1

Open
rdmclin2 opened this issue May 1, 2018 · 0 comments
Open

这个jsx不太冷 #1

rdmclin2 opened this issue May 1, 2018 · 0 comments
Labels

Comments

@rdmclin2
Copy link
Owner

rdmclin2 commented May 1, 2018

文章名称来自豆瓣高分电影列表, 电影名为《这个杀手不太冷》

前言

我们假设你有一定的前端知识,简单使用过React但不了解他的原理以及为什么要用它。

通过这篇文章你会了解到这些问题的答案:

  1. 为什么你自己写的组件要首字母大写?
  2. 为什么能在js文件里写html的语法?
  3. 为什么jsx的方式要比模板强?
  4. 为什么jsx必须要有一个顶层节点?
  5. 为什么jsx中不能用class要用className?
  6. 为什么react-dom要单独作为一个库?
  7. 为什么我即使没在这个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表示的代码行数你就知道了:

  1. 太长
  2. 不清晰

所以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上,渲染到手机上。

总结下过程:

// 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,总结一下原因有两点:

  1. 我们在操作html属性的时候一般用的是el.className=...,而不是el.setAttribute('class', ...),Attributes一般赋值字符串,而属性名可以赋值对象,更灵活。所以jsx中的className和HMTL的className属性表现一致,没毛病
  2. 未来react可能会用…来解构this.props,这时候class和for作为保留字不能作为变量标识,就不能适用这种情况了。

求带啊,有没有其他React技巧

来来来,新鲜的React技巧便宜卖,10元一条,请扫我的付款码…好吧,其实都是jsx-in-depth里的。

  • 属性可以用字符串字面量, 会进行解码
<MyComponent message="&lt;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 :)

参考文章

@rdmclin2 rdmclin2 added the Blog label May 1, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant