《深入浅出React和Redux》读书笔记(一)

Author Avatar
wshunli 11月 10, 2017
  • 在其它设备中阅读本文章

发现马上就要入前端的大坑了。ES6入门内容很多还没看完,现在先学习 React 吧。

之间就简单地使用 React 搭建过一次 Demo ,借这次机会把 React 给搞定了,至少能写个简单的界面。

React技术栈

看着这 React技术栈 ,还是压力山大啊。

这次选了两本书 《深入浅出React和Redux》 和 《React全栈》。书都很新也很薄,这次先看一本入门。

第1章 React 新的前端思维方式

首先使用 creat-react-app 创建一个 React 应用。

在确认 Node.js 和 npm 安装好之后,在命令行执行以下命令安装 creat-react-app 工具。

npm install -g create-react-app
create-react-app first-react-app
npm start

浏览器会自动打开 http://localhost:3000/ 显示如下界面:

first-react-app

我们来看一下 first-react-app 的目录结构:

first-react-app

在开发过程中,我们主要关注 src 目录的内容。

其中 src/index.js 文件是应用的入口文件:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';

ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();

这里的代码渲染了一个 App 组件,效果就是首页界面。
我们看下 App 组件是怎么定义的,在 src/App.js 文件中:

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {
  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
      </div>
    );
  }
}

export default App;

React 应用实际上依赖很复杂的技术栈,我们使用 creat-react-app 避免一开始就浪费太多精力配置技术栈。

我们启动 React 的命令是 npm start ,在 package.json 中:

  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }

npm build 可以创建生产环境优化代码;
npm teat 用于单元测试;
npm eject 把 react-scripts 技术栈配置都弹射到应用顶层,不可逆。

添加 React 组件

仿照着增加一个新的 React 组件。同样在 src 目录新建文件 ClickCounter.js 写入如下代码:

import React, { Component } from 'react';

class ClickCounter extends Component {

  constructor(props) {
    super(props);
    this.onClickButton = this.onClickButton.bind(this);
    this.state = {
      count: 0
    }
  }

  onClickButton() {
    this.setState({count: this.state.count + 1});
  }

  render() {
    const counterStyle = {
      margin: '16px'
    }
    return (
      <div style={counterStyle}>
        <button onClick={this.onClickButton}>Click Me</button>
        <div>
          Click Count: <span id="clickCount">{this.state.count}</span>
        </div>
      </div>
    );
  }
}

export default ClickCounter;

修改 src/index.js 文件:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import ClickCounter from './ClickCounter';
import registerServiceWorker from './registerServiceWorker';

ReactDOM.render(<ClickCounter />, document.getElementById('root'));
registerServiceWorker();

其中在 index.js 中使用 import 导入 ClickCounter 组件,替代之前的 App 组件。

import ClickCounter from './ClickCounter';

在 ClickCounter.js 的第一行我们以同样的方式引入 React 和 Component 。

import React, { Component } from 'react';

Component 为所有组件的基类,提供很多组件共有的功能。

class ClickCounter extends Component {}

React 没有在代码中直接使用,但在 JSX 使用的范围内必须有 React,因为 JSX 最终会被转译成依赖 React 的表达式。

JSX:JSX 是 JavaScript 的一种扩展语法,使我们能够在 JavaScript 中编写类似 HTML 的代码。
JSX 的基本语法规则:遇到 HTML 标签(以<开头),就用 HTML 规则解析;遇到代码块(以{开头),就用 JavaScript 规则解析。

首先,在 JSX 中使用的元素不限于 HTML 元素,可以是 React 组件。而区分二者的方法是首字母是否大写。
其次,在 JSX 中可以通过 onClick 这样的方式为元素添加事件处理函数。

<button onClick={this.onClickButton}>Click Me</button>

React 的工作方式

React的理念归结为一个公式:UI=render(data) 。

用户看到的界面(UI)是一个函数(render)的执行结果,只接受数据(data)作为参数。这是一个纯函数,即输出只依赖于输入的函数,两次函数的调用如果输入相同,那么输出也绝对相同。如此一来,最终的用户界面,在render函数确定的情况下完全取决于输入数据。

对于开发者来说,重要的是区分开哪些属于data ,哪些属于render ,想要更新用户界面,要做的就是更新data ,用户界面自然会做出响应,所以React 实践的也是“响应式编程”( Reactive Programming )的思想。

Virtual DOM:是对DOM树的抽象,它并不触及浏览器部分,只是存在于 JavaScript 空间的树形结构,每次在渲染 React 组件,React 会对前后两次产生的 Virtual DOM 进行比较,最后只有发生了改变的地方会被重新渲染。

总而言之,React利用函数式编程的思维来解决用户界面渲染的问题,强制所有组件都以数据驱动渲染的模式进行开发。

第2章 设计高质量的 React 组件

作为软件设计的通则,组件的划分要满足高内聚(High Cohesion )和低搞合( Low Coupling)的原则。

React 组件的数据

React 组件的数据分为两种, prop 和 state。

prop 是组件的对外接口, state 是组件的内部状态,对外用 prop ,内部用 state 。

React 的 prop

prop ( property 的简写)是外部传递给组件的数据, React 组件通过定义自己能够接受的 prop 就定义了自己的对外公共接口。

<SampleButton
id= "sample" borderWidth={2} onClick={onButtonClick}
style={{color :"red"}}
/>

当 prop 的值不是字符串类型时,在 JSX 中必须用花括号 {} 包住,
所以 style 的值有两层花括号,外层花括号代表是 JSX 的语法,内层的花括号代表这是一个对象常量。

class ControlPanel extends Component {
  render() {
    console.log('enter ControlPanel render');
    return (
      <div>
        <Counter caption="First" initValue={0}/>
        <Counter caption="Second" initValue={10} />
        <Counter caption="Third" initValue={20} />
      </div>
    );
  }
}

上面是给 prop 赋值,下面读取 prop 的值:

class Counter extends Component {

  constructor(props) {
    console.log('enter constructor: ' + props.caption);
    super(props);

    this.onClickIncrementButton = this.onClickIncrementButton.bind(this);
    this.onClickDecrementButton = this.onClickDecrementButton.bind(this);

    this.state = {
      count: props.initValue || 0
    }
  }
  ···

在构造函数中可以通过参数 props 获得传人 prop 值,在其他函数中则可以通过 this.props 访问传人 prop 的值。

  ···
 render() {
    console.log('enter render ' + this.props.caption);
    const {caption} = this.props;
    return (
      <div>
        <button style={buttonStyle} onClick={this.onClickIncrementButton}>+</button>
        <button style={buttonStyle} onClick={this.onClickDecrementButton}>-</button>
        <span>{caption} count: {this.state.count}</span>
      </div>
    );
  }

propTypes 检查:组件声明接口规范,即组件支持的 prop 及其格式。

Counter.propTypes = {
  caption: PropTypes.string.isRequired,
  initValue: PropTypes.number
};

建议在开发阶段使用,产品环境下去除。

React 的 state

state 代表组件的内部状态。

1、初始化 state

通常在组件的构造函数结尾处初始化 state 。

constructor(props) {
    ···
    this.state = {
      count: props.initValue || 0
    }
  }

React 的 defaultProps 给 prop 默认初始值:

Counter.defaultProps = {
  initValue: 0
}
// this.state 可省去判断
this.state = {
  count: props.initValue
}

2、读取更新 state

this.state 读取 state
this.setState 更新 state

onClickIncrementButton() {
  this.setState({count: this.state.count + 1});
}

prop 和state 的区别

  • prop 用于定义外部接口, state 用于记录内部状态;
  • prop 的赋值在外部世界使用组件时, state 的赋值在组件内部;
  • 组件不应该改变 prop 的值,而 state 存在的目的就是让组件来改变的。

组件是绝不应该去修改传人的 props 值,否则会影响其他组件。

组件的生命周期

React 严格定义了组件的生命周期,生命周期可能会经历如下三个过程:

  • 装载过程( Mount ),也就是把组件第一次在DOM 树中渲染的过程;
  • 更新过程( Update ),当组件被重新渲染的过程;
  • 卸载过程( Unmount ),组件从DOM 中删除的过程。

三种不同的过程, React 库会依次调用组件的一些成员函数,这些函数称为生命周期函数。所以,要定制一个 React 组件,实际上就是定制这些生命周期函数。

React组件生命周期

https://reactjs.org/docs/react-component.htm

装载过程

当组件第一次渲染的时候,依次调用的函数如下:

  • constructor()
  • getlnitialState()
  • getDefaultProps()
  • componentWillMount()
  • render()
  • componentDidMount()

1、constructor()
ES6 中类的构造函数,主要是初始化 state 和绑定成员函数的 this 环境。

2、getlnitialState() 和 getDefaultProps()
getlnitialState() 返回值用来初始化组件的 this.state ,getDefaultProps() 返回值用来作为 prop 的初始值。
这两个方法只有用 React.createClass 方法创建组件类时才会生效。

const Sample = React.createClass({
  getInitialState() {
    console.log('enter getInitialState');
  }
  getDefaultProps() {
    console.log('enter getDefaultProps');
  }
});

而在 ES6 中使用 defaultProps 指定初试 prop 初始值。

const Sample extends React.Component({
  constructor(props) {
    super(props);
    this.state = {foo: "bar"}
  }
}

Sample.defaultProps = {
  return {sampleProp: 0}
};

React.createClass 已经被 Facebook 官方逐渐废弃。

3、render()

render() 函数在 React 中必须实现,其他生命周期函数在 React.Component 中都有默认实现。

render() 并不做实际的渲染动作,只是返回 JSX 描述的结构,最终由 React 操作渲染过程。
不需要渲染界面可以返回 null 或者 false 。

4、componentWillMount() 和 componentDidMount()

在装载过程中,componentWillMount() 会在调用 render() 函数之前被调用, componentDidMount() 会在调用 render() 函数之后被调用。

componentWillMount() 是将要装载,虽然还没有渲染出来结果,但是修改组件状态已经晚了。
componentDidMount() 是 render() 函数返回的内容已经渲染好了,组件已经被装载在 DOM 树上了。

componentWilIMount() 和 componentDidMount() 这对兄弟函数还有一个区别:
componentWillMount() 可以在服务器端被调用,也可以在浏览器端被调用;
而componentDidMount() 只能在浏览器端被调用,在服务器端使用 React 的时候不会被调用。

更新过程

当组件的 props 或者 state 被修改的时候,就会引发组件的更新过程。
更新过程会依次调用下面的生命周期函数:

  • componentWillReceiveProps(nextProps)
  • shouldComponentUpdate(nextProps, nextState)
  • componentWillUpdate()
  • render()
  • componentDidUpdate()

1、componentWillReceiveProps(nextProps)

只要是父组件的 render() 函数被调用,在 render() 函数里面被谊染的子组件就会经历更新过程,
不管父组件传给子组件的 props 有没有改变,都会触发子组件的 componentWillReceiveProps() 函数。

componentWillReceiveProps(nextProps) 适合根据新的 props 值(也就是参数 nextProps )来计算出是不是要更新内部状态state 。

注意,更新组件内部状态的方法 this.setState() 方法触发的更新过程不会调用这个函数,否则会导致 componentWillReceiveProps 再次被调用,陷入死循环。

当组件的 props 发生改变:

"componentWillReceiveProps"
"shouldComponentUpdate"
"componentWillUpdate"
"render"
"componentDidUpdate"

当组件的 state 发生改变:

"shouldComponentUpdate"
"componentWillUpdate"
"render"
"componentDidUpdate"

当父组件导致子组件重新渲染:

"componentWillReceiveProps"
"shouldComponentUpdate"
"componentWillUpdate"
"render"
"componentDidUpdate"

2、shouldComponentUpdate(nextProps, nextState)

shouldComponentUpdate() 函数决定组件是否需要渲染,返回布尔值。

shouldComponentUpdate(nextProps, nextState) {
  return (nextProps.caption !== this.props.caption) ||
         (nextState.count !== this.state.count);
}

3、componentWillUpdate() 和 componentDidUpdate()

当组件的 shouldComponentUpdate() 函数返回 true ,React 接下来依次调用对应组件的 componentWillUpdate()、render() 和 componentDidUpdate() 函数。

和装载过程不同的是,当在服务器端使用 React 渲染时,二者都可以在服务端调用。

卸载过程

componentWillUnmount() 表示组件要从 DOM 树上删除掉之前,比较适合做一些清理工作。

案例展示

ControlPanel.js

import React, { Component } from 'react';
import Counter from './Counter.js';

const style = {
  margin: '20px'
};

class ControlPanel extends Component {

  constructor(props) {
    super(props);

    this.onCounterUpdate = this.onCounterUpdate.bind(this);

    this.initValues = [ 0, 10, 20];

    const initSum = this.initValues.reduce((a, b) => a+b, 0);
    this.state = {
      sum: initSum
    };
  }

  onCounterUpdate(newValue, previousValue) {
    const valueChange = newValue - previousValue;
    this.setState({ sum: this.state.sum + valueChange});
  }

  render() {
    return (
      <div style={style}>
        <Counter onUpdate={this.onCounterUpdate} caption="First" />
        <Counter onUpdate={this.onCounterUpdate} caption="Second" initValue={this.initValues[1]} />
        <Counter onUpdate={this.onCounterUpdate} caption="Third" initValue={this.initValues[2]} />
        <hr/>
        <div>Total Count: {this.state.sum}</div>
      </div>
    );
  }
}

export default ControlPanel;

Counter.js

import React, { Component, PropTypes } from 'react';

const buttonStyle = {
  margin: '10px'
};

class Counter extends Component {

  constructor(props) {
    super(props);

    this.onClickIncrementButton = this.onClickIncrementButton.bind(this);
    this.onClickDecrementButton = this.onClickDecrementButton.bind(this);

    this.state = {
      count: props.initValue
    }
  }

  onClickIncrementButton() {
    this.updateCount(true);
  }

  onClickDecrementButton() {
    this.updateCount(false);
  }

  updateCount(isIncrement) {
    const previousValue = this.state.count;
    const newValue = isIncrement ? previousValue + 1 : previousValue - 1;

    this.setState({count: newValue})
    this.props.onUpdate(newValue, previousValue)
  }

  render() {
    const {caption} = this.props;
    return (
      <div>
        <button style={buttonStyle} onClick={this.onClickIncrementButton}>+</button>
        <button style={buttonStyle} onClick={this.onClickDecrementButton}>-</button>
        <span>{caption} count: {this.state.count}</span>
      </div>
    );
  }
}

Counter.propTypes = {
  caption: PropTypes.string.isRequired,
  initValue: PropTypes.number,
  onUpdate: PropTypes.func
};

Counter.defaultProps = {
  initValue: 0,
  onUpdate: f => f //什么都不做的函数
};

export default Counter;

参考资料
1、React.Component - React
https://reactjs.org/docs/react-component.htm
2、React组件生命周期
https://nsne.github.io/2017/02/15/react-component-lifecycle/

如果本文对您有所帮助,且您手头还很宽裕,欢迎打赏赞助我,以支付网站服务器和域名费用。 https://paypal.me/wshunli 您的鼓励与支持是我更新的最大动力,我会铭记于心,倾于博客。
本文链接:https://www.wshunli.com/posts/d0f6201d.html