发现马上就要入前端的大坑了。ES6入门内容很多还没看完,现在先学习 React 吧。
之间就简单地使用 React 搭建过一次 Demo ,借这次机会把 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 的目录结构:
在开发过程中,我们主要关注 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 组件,实际上就是定制这些生命周期函数。
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/
评论 (0)