react 知识梳理(一)
这几天读了胡子大哈老师的《React.js 小书》,感觉获益良多,真心推荐大家可以看一看!
JSX 原理
jsx 的本质是 React.createElement 的语法糖,在 React 文档中,有这么一段话:
JSX 对使用React 不是必须的。当你不想在你的构建环境中设置编译器,那么不使用 JSX 的 React 是非常方便的。
每一个 JSX 元素都是调用 React.createElement(component, props, ...children) 的语法糖,因此,任何你使用 JSX 来做的事都可以通过纯 JavaScript 实现。
<div class="root">
<div class="child">hello man</div>
</div>
观察以上代码,我们可以知道,每个 DOM 其实都只包含了三个信息:标签名称、属性、子元素。因此,每个 DOM 元素,我们都可以用一个 js 对象来标示。上面这段代码我们可以这样表示:
{
tag: 'div',
attr: {
class: 'root'
},
child: {
tag: 'div',
attr: {
class: 'child'
},
child: 'hello man'
}
}
在 React 中,我们使用 React.createElement 来将上面的这种 js 对象来转化成为真正的 DOM 元素,至于 React.createElement 的内部实现,这里暂不做深究。
如果我们直接在代码中调用 React.createElement ,通过这种 js 对象的方式来生成 DOM 的话,代码看起来会有些冗长繁琐,不太符合我们追求简单的想法,于是,React 发明了 JSX 语法,通过 JSX ,我们可以直接在 js 代码中书写 html 结构,最后交给 babel 来编译为上面这种 js 描述 DOM 的对象,最后通过 React.createElement 来构建真正的 DOM。
容器组件
在一些业务场景下,我们或许需要处理大量的具有相同整体布局可是包含子节点的布局内容却又完全不同的 DOM,例如下图所示:

我们可以看到,虽然左右两个元素中的内部元素完全不同,可是却又有着相同的整体布局!这个时候,虽然我们可以用相同的命名,引入相同的 css 来减少我们的工作量,可是,当这些元素分别位于不同的组件时,我们就要为每个组件都引入同一份 css ,还要给他们同样的类名,显然,这样有些繁琐。如果这时用一个可以公用的容器组件,我们只需要引入这个容器组件,然后往里边填充相应的内容即可,显然可以让代码显得更加整洁,使用起来也更加方便。
我们用 create-react-app 新建一个 React 项目(示例代码请点这里),清除掉一些不相干的内容,让我们的项目看起来更加清晰,然后新建两个组件:

Container.js
import React from 'react';
export default class Container extends React.Component {
constructor(){
super()
}
render(){
return (
<div>
Container
</div>
)
}
}
head.js
import React from 'react';
export default class Head extends React.Component {
constructor() {
super()
}
render() {
return (
<div>
head
</div>
)
}
}
我们在 index.js 中引入这两个组件,将 Head 组件插入 Container 组件中:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Container from './container';
import Head from './head';
ReactDOM.render(
<Container>
<div>hello man</div>
<Head></Head>
</Container>,
document.getElementById('root')
);
打开浏览器,我们看到,在页面上,并没有出现 head ,也没有出现 hello man,而是显示出了 container :

我们在控制台将 container 打印出来:

可以看到在 container 的 props.children
属性中,储存着我们插入进 Container 组件的内容,我们只需要将这些信息渲染出来就可以了,我们改写一下 Container 组件的代码:
import React from 'react';
export default class Container extends React.Component {
constructor(){
super()
}
render(){
return (
<div className="container">
<div className="head">{this.props.children[0]}</div>
<div className="body">{this.props.children[1]}</div>
</div>
)
}
}
打开浏览器,我们看到,之前填充在 Container 组件中的内容已经正常显示,并且整体布局结构也和我们预期一样。我们可以根据具体的业务,来填充具体的内部元素。
这里有个问题需要注意,当我们注释掉插入 Container 组件的 Head 组件时,我们发现,插入的 hello man
也不能正常显示:
ReactDOM.render(
<Container>
<h1>hello man</h1>
{/* <Head></Head> */}
</Container>,
document.getElementById('root')
);


props.children
已经不再是一个数组,而是一个对象,所以我们在 Container 组件内部通过下标来取值的做法,显然是有问题的。我们需要来对props.children
进行判断,来确定取值方式,具体实现并不难,这里就不再写示例代码了。
高阶组件(文档位置)
首先,我们来看一下这些定义:
高阶组件是一个函数,能够接受一个组件并返回一个新的组件。
高阶组件是一个纯函数,不会改变原来的组件,没有副作用。
简单来说,满足下面条件的函数可以称之为纯函数:
返回结果完全依赖传入的参数。
执行过称中没有副作用。
同样的输入得到同样的输出。const gen = Math.random() 就不是纯函数。(感谢FateRiddle指正)
我们来看下面这两个函数,了解下什么是返回结果完全依赖传入的参数:
let d = 1;
function add(a,b){
return a + b
}
function all(c){
return c + d
}
当我们执行 add
和 all
时,add
的返回结果完全依赖于传入的参数 a
和 b
的数值,add(3,6)
一定返回 9,可是当 all
执行时, all(3)
的返回之就不一定是 4,当 d
的值变为 2 的时候, all(3)
的返回值就变成了 5,所以,在这里 all
就不能称之为是一个纯函数,而 add
则是一个纯函数。
可是当我们重新声明一个变量,改写下add
:
let obj = {
x: 2
}
function add(obj,b){
obj.x = 1;
return obj.x + b
}
我们再次调用 add(obj,6)
,这个时候,虽然 add
的返回结果依旧完全依赖与传入的参数,但是,传入的 obj
对象的 x
属性的值却发生了变化,这就是产生的副作用,所以,add
就不是一个纯函数。
而高阶函数是一个纯函数,不会改变传入的组件。
说了这么多,可是高阶组件有哪些用处呢?
我们来看下面这两段代码:
组件一:
import React from 'react';
export default class One extends React.Component {
constructor(props) {
super(props)
this.state = {
higher: 0
}
}
componentWillMount(){
let higher = this.props.higher * 2
this.setState({
higher: higher
})
}
render() {
return (
<div>{this.state.higher}</div>
)
}
}
组件二:
import React from 'react';
export default class Two extends React.Component {
constructor(props) {
super(props)
this.state = {
higher: 0
}
}
componentWillMount(){
let higher = this.props.higher * 2
this.setState({
higher: higher
})
}
render() {
return (
<h1>{this.state.higher}</h1>
)
}
}
我们看到,除了组件返回的标签元素外,其他代码完全相同。两个组件都在 componentWillMount
时对传入的数据做了逻辑处理,这时,我们就可以利用高阶组件,将公共的逻辑代码抽离出来:
const higher = function(Component,data){
class Higher extends React.Component {
constructor(props) {
super(props)
this.state = {
higher: 0
}
}
componentWillMount() {
let higher = data * 2
this.setState({
higher: higher
})
}
render() {
return (
<Component higher={this.state.higher}></Component>
)
}
}
return Higher
}
我们将原始组件和高阶组件插入页面:
let CopyOne = higher(One,30);
let CopyTwo = higher(Two, 40);
ReactDOM.render(
<div>
<CopyOne></CopyOne>
<CopyTwo></CopyTwo>
<One higher={10}></One>
<Two higher={20}></Two>
{/* <Container>
<h1>hello man</h1>
<Head></Head>
</Container> */}
</div>,
document.getElementById('root')
);
打开浏览器我们看到:

高阶组件的使用,极大的提高了我们代码的复用性,在高阶组件函数内部,我们可以根据具体业务,十分灵活的对组件进行改造,生成符合我们业务需求的新组件。
最后的话:
这篇笔记简要的梳理了一下自己对 React 的理解,若有描述不对之处,还望大家批评指正!