最重要的一点:react 是一个完全遵从 javascript 开发模式的类库,语言层面仅仅有几个 API 而已,没有配置。react 更多的是需要开发者从设计模式上去考虑如何构建一个 web 项目。

概念

react 主要有两方面的功能,

  1. 根据数据构建虚拟树
  2. 根据树构建 web 页面

react 开发者仅仅需要关注于第一点即可,后续步骤由 react 按一定顺序进行下去,不需要开发者关心。

虚拟树

一个网页的 html 的代码就可以大致被认为是一个树结构(DOM 树),而虚拟树就是为了模拟 DOM 树结构存在的一种结构。

有了虚拟树结构,我们就能将页面的结构保存在 js 环境下,这样 js 的操作就可以不用依赖 DOM API ,也就是当数据更新的时候我们仅仅需要的是重新生成一个虚拟树即可,至于后续的从虚拟树生成 web 页面我们不关心。

那么如何生成虚拟树?

使用 React.createElement() 即可生成一个树节点。但在 react 中并不直接使用该函数(当然直接使用也可以),大部分情况下使用 jsx

React.createElement

该方法需要传入 3 个参数,节点名称(或是组件),props,子节点(由 React.createElement 创建,或纯文字)。

// 一个 p 节点,带了一些 props,没有子节点
let p = React.createElement('p', {className: 'line', propsName: 'some'});
// 一个 span 节点,带了一些 props,有一个纯文字子节点
let span = React.createElement('span', {className: 'text', propsName: 'some'}, ['some text']);
// 一个 div 节点,带了一些 props,有 p 和 span 两个子节点
let divTree = React.createElement('div', {className: 'box', propsName: 'some'}, [p, span]);

console.log(divTree)

以上代码可在有 React 的环境下执行,可查看最终的结果。

React.createElement

可以看出最终输出的对象通过 props.children 字段实现了子节点的保存。

最新版本的 React.createElement 可以通过 (type, props, ...children) 获取子节点。

jsx

jsxjs 的语法扩展,目的是将 React.createElement 这个方法简洁化,将编程的写法改成描述的写法,我们来改写上面的例子:

let divTree = (
<div className="box" propsName="some">
    <p className="line" propsName="some"></p>
    <span className="text" propsName="some">some text</span>
</div>
)

这样就更贴近了 html 语法,并且比编程的写法更容易让人弄清虚拟树的结构。

在正常的 js 环境中是不认识 jsx 语法的,可以在 babelrepl 中将 jsx 翻译成 js 语法。

jsx 的更多细节

如何在 jsx 中使用 js

let num = 123;
let divTree = (
<div className="box" propsName="some">
    <p className="line" propsName="some">{num}</p>
    <span className="text" propsName="some">some text</span>
</div>
)

将表达式用 {} 包含即可。

虚拟树 to Dom

有了虚拟树我们就可以把虚拟树渲染到浏览器上:

ReactDom.render(divTree, document.body);

react 组件的两种存在形式

ok 了解了虚拟树的生成,那么如何去组织这个虚拟树的生成?这就涉及到 react 组件。

函数组件

let Com1 = function(props){
    console.log(props);
    props.children
    return (
        <div>
            <p>{props.propsName}</p>
        <div>
    )
}

函数组件最终返回一个虚拟树结构,注这里不是 html 代码。

那么如何使用?

// 调用组件时,参一 为组件的引用
let com1 = React.createElement(Com1, {propsName: 'some'}, 'some text');
// 非组件调用时 参一 为字符串
let divTree2 = React.createElement('div', {propsName: 'some'}, com1);

console.log(divTree2);
ReactDOM.render(divTree2, document.body);

组件可以作为 React.createElement 的第一个参数传入,React.createElement 检测到 第一个参数为函数时,将处理好的 props 作为参数调用它,获得函数的返回,处理后作为调用 React.createElement 的调用返回。

那么 jsx 呢?该怎么写,正常写就 ok

let divTree2 = (
    <div propsName="some">
        <Com1 propsName="some">some text</Com1>
    </div>
)

这里需要注意,由于 babel 的转换的原因,如果是组件那么组件的引用的名字必须大写。

类组件

class Com2 extends React.Component {
    render(){
        let props = this.props;
        return (
            <div>
                <p>Com1 text</p>
            <div>
        )
    }
}

let tree = new Com2(props).render()

Com2Com1 实现的功能完全一致。调用方式也完全一致,区别在于 React.createElement 检测到第一个参数为类时,会将处理好的 props 作为类实例化时的参数,并在实例化完成时调用 render 函数,获取返回值处理后作为 React.createElement 函数调用的返回。

两种组件的区别

除了类组件多了一些生命钩子和 state 其他的区别其实都是 js 语法中类和函数的区别,这里不深究。

关于事件

react 库本身实现了事件系统,这不是浏览器里面的事件系统,所以也就不能和浏览器的事件系统混用。

react 中的事件是根据 W3C 标准事件模型实现的,也就是说 react 里的事件能阻止冒泡到 react 的事件上,但不能阻止正常的浏览器标准事件。

而浏览器的事件是能阻止事件冒泡到 react 事件中的,原因是 react 把所有的事件都注册到了 document 上,所有浏览器的事件如果阻止冒泡到 document 上,那么 react 事件系统里面的相应事件就执行不了了。

结论:只用 react 的事件系统,如果不得已使用浏览器的事件系统,在明确结果的情况下阻止冒泡。

资源

React.js 小书