问题:
- 为什么使用JSX声明组件时,最外层的组件根元素只允许使用单一根元素。?
- className而不是class,htmlFor而不是for?
- 自定义的组件都必须要用大写字母开头,普通的 HTML 标签都用小写字母开头?
- 为什么注释是{/*……..*/}而不是或者/*……..*/?
JSX是什么
It is called JSX, and it is a syntax extension to JavaScript. We recommend using it with React to describe what the UI should look like. JSX may remind you of a template language, but it comes with the full power of JavaScript.
JSX 其实就是 JavaScript 的一种扩展语法。JSX 的官方定义是类 XML 语法的 ECMAscript 扩展,完美地利用了 JavaScript 自带的语法和特性,并使用大家熟悉的 HTML 语法来创建虚拟元素。Babel 将JSX编译成 React.createElement() 调用。
下面是一个简单的jsx例子:
|
|
被Babel编译成:
解释1:为什么使用JSX声明组件时,最外层的组件根元素只允许使用单一根元素?
因为 JSX 语法会被转化为 React.createElement(component,props,…children) 调用,而该函数的第一个参数只允许传入单元素,而不允许传入多元素。
解释2:className而不是class,htmlFor而不是for?
因为class和for都是javascript关键字,所以 React.js 中定义了一种新的方式:className和htmlFor。
解释3:JSX标签的第一个部分指定了React元素的类型,首字母大写的标签说明了它是一个引用的React组件,这些标签会被编译称为一个命名属性的直接引用,所以如果需要使用JSX
解释4:为什么注释是{/……../}而不是或者/……../?
JSX内部的注释只是 JS 表达式。你只需要在一个标签的子节点内(非最外层)小心地用 {} 包围要注释的部分。最外层的注释可以用//
so:
- React DOM 使用驼峰(camelCase)属性命名约定;
- 默认情况下,在渲染之前,React DOM 会格式化(escapes) JSX中的所有值,所有的数据都被转义成字符串处理,以避免 XSS(跨站脚本) 攻击。
- ……
React.createElement()解析
在官网createElement()是这样定义的:
创建并返回一个新的给定类型的 React 元素 。这个 type(类型) 参数可以是一个标签名字字符串(例如 ‘div’ 或’span’),或者是一个 React 组件类型(类或者是函数)。
官网上建议使用 JSX 来描述你的 UI 应该是什么样。每个 JSX 元素都是调用 React.createElement() 的语法糖。 如果使用 JSX ,你不必显示地调用该方法。
上源码:
ReactElement源码:
JSX是如何在React中生效的
前面说了,如果使用 JSX,你不必显示地调用该方法。那是因为在mountComponent()方法中会进行组件渲染,调用instantiateReactComponent()方法。instantiateReactComponent方法会根据node传参的不同类型,创建不同的组件对象。源码如下:
|
|
从instantiateReactComponent源码中可见,针对不同的node,有以下几种方法:
- new ReactEmptyComponent(instantiateReactComponent):node为null
- ReactNativeComponent.createInternalComponent(element):node是对象且node.type为string(原生dom对象)。调用ReactDOMComponent(tag)生成。做了安全性校验(防止注入)、大小写转换等等,详见源码。
- new ReactCompositeComponentWrapper():node是对象且type为function(React自定义组件)。详情见ReactCompositeComponentMixin方法。提供的mountComponent方法实现了对React子组件的不断递归。
- ReactNativeComponent.createInstanceForText(node):node是字符串或者数字(文字 or 数字)。详情见方法ReactDOMTextComponent
下面是React.render时触发的jsx流程图:
当state变化或者props变化时,dom是如何重新渲染的呢?前面知道了,不同的元素类型已经实例化了相应的组件了。每个组件里除了有mountComponent外,还有一个receiveComponent方法。他来实现组件的重新渲染。下面是流程图:
源码看到这里,你可能会想到react的diff算法。diff算法入口就是ReactDOMComponent里的_updateDOMChildren方法。这个不难理解,所有的React组件分解到最后都是dom的处理(或者字符串数字的简单处理)。所以diff算法在_updateDOMChildren,用来处理虚拟dom树的更新。具体算法这里就不做分析了~
事件监听
在 React.js 不需要手动调用浏览器原生的 addEventListener 进行事件监听。React.js 帮我们封装好了一系列的 on 的属性,当你需要为某个元素监听某个事件的时候,只需要简单地给它加上 on 就可以了。而且你不需要考虑不同浏览器兼容性的问题,React.js 都帮我们封装好这些细节了。
没有经过特殊处理的话,这些 on* 的事件监听只能用在普通的 HTML 的标签上,而不能用在组件标签上。也就是说,
下面看源码,首先查找ReactDOMComponent下mountComponent方法,发现针对不同的tag类型,React提供了相应的dom组件,并调用mountWrapper方法。随便选择一个,例如select,ReactDOMSelect.mountWrapper(this, props, context);看这个mountWrapper方法:
它提供了onChange的属性接口,并通过_handleChange方法定义。
同理,其他的dom组件都有mountWrapper方法,并定义了一些接口方法。所以,我们在写react时需要按照接口规范些onClick或者onChange等方法。并且我们可以看到React组件对象ReactCompositeComponent的mountComponent方法内就没有这种方法的接口定义,所以,
事件中的 this
我们在React开发中离不开this的使用,常常会遇到一些问题。所以最后再简单讨论下JSX中的this使用。
this具体是由其上下文决定的。
也就是说,一个Component组件里面的this,是在它被实例化后,才被确定下来,指向这个实例。(我们在ReactCompositeComponent的mountComponent方法内就能找到new Component方法)。但是在JSX中我们经常遇到困惑,例如,在render里写子组件,并且传递方法。这个方法里调用的this到底指向哪个呢?还是要看它的上下文。如下面的例子:
子组件TodoList中onClick方法调用了this.props.removeFunc方法,即调用父组件传入的removeFunc方法。但是是由子组件调用的,所以一般情况下(未绑定this时),removeFunc方法上下文的this是子组件TodoList。这里我们在父组件传递removeFunc时将this做了绑定,确定指向父组件,才能操作items参数,达到删除的作用。
es6的箭头函数,直接使用.bind(this)都可以达到绑定this到其定义的上下文中的作用。
我们也可以看看源码里事件监听的处理方式:
可见React没有对this绑定进行特殊处理,开发者自己根据需要定制绑定即可。