React基础

2023-11-28 16:34:38 浏览数 (2)

1. React简介

React是一个用于构建用户界面的JavaScript库。

React主要用于构建UI,很多人认为React是MVC中的V(视图)。

React起源于FaceBook的内部项目,用来架设Instagram的网站,并于2013年5月开源。

React拥有较高的性能,代码逻辑非常简单,越来越多的人已开始关注和使用它。

1.1 React的特点

  1. 声明式设计——React采用声明范式,可以轻松描述应用。
  2. 高效——React通过对DOM的模拟,最大限度地减少与DOM的交互。
  3. 灵活——React可以与已知的库或框架很好地配合。
  4. JSX——JSX是JavaScript语法的扩展。React开发不一定使用JSX,但建议使用它。
  5. 组件——通过React构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中。
  6. 单向响应的数据流——React实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统的数据绑定更简单。

1.2 相关js库

  • babel.min.js:作用是将ES6–>ES5, JSX --> JS。Babel可以将ES6代码转为ES5代码,这样我们就能在目前不支持ES6浏览器上执行React代码。Babel内嵌了对JSX的支持。通过Babel和babel-sublime包(package)一同使用可以让源码的语法渲染上升到一个全新的水平。
  • react.development.js:react核心库
  • react-dom.development.js:react扩展库

1.3 Hello World

以下是一个helloWorld的实例:

代码语言:javascript复制
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>Hello World</title>
	</head>
	<body>

		<!-- 准备好一个容器 -->
		<div id="demo"></div>


		<!-- 引入react核心库 -->
		<script type="text/javascript" src="js/react.development.js"></script>
		<!-- 引入react-dom,用于支持react操作dom -->
		<script type="text/javascript" src="js/react-dom.development.js"></script>
		<!-- 引入babel,用于将jsx转化为js -->
		<script type="text/javascript" src="js/babel.min.js"></script>

		<script type="text/babel">
			ReactDOM.render(
				<h1>Hello, world!</h1>,
				document.getElementById("demo")
			)
		</script>
	</body>
</html>

或者使用create-react-app工具创建的react开发环境

代码语言:javascript复制
import React from "react";
import ReactDOM from "react-dom";

function Hello(props){
	return <h1>Hello World!</h1>
}

ReactDOM.render(<Hello />, document.getElementById("demo"));

这时候浏览器打开http://localhost:3000/就会输出:

代码语言:javascript复制
Hello World!

2. React安装

React可以直接下载使用,官网下载链接:https://reactjs.org/

也可以在项目中直接使用Staticfile CDN 的React CDN库,地址如下:

代码语言:javascript复制
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<!-- 生产环境中不建议使用 -->
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>

2.1 通过npm使用React

建议在React中使用CommonJS模块系统,比如browserify或webpack。

国内使用npm速度较慢,可以使用淘宝定制的cnpm(gzip压缩支持)命令行工具代替默认的npm:

代码语言:javascript复制
$ npm install -g cnpm --registry=https://registry.npmmirror.com
$ npm config set registry https://registry.npmmirror.com

这样就可以使用 cnpm 命令来安装模块了:

代码语言:javascript复制
$ cnpm install [name]

淘宝镜像官网:http://npm.taobao.org/

2.2 使用create-react-app快速构建React开发环境。

create-react-app 是来自于 Facebook,通过该命令我们无需配置就能快速构建 React 开发环境。

create-react-app 自动创建的项目是基于 Webpack ES6 。

执行以下命令创建项目:

代码语言:javascript复制
$ cnpm install -g create-react-app
$ create-react-app my-app
$ cd my-app/
$ npm start

在浏览器打开http://localhost:3000/,就可以看到React启动的效果。

2.3 React项目的目录结构

代码语言:javascript复制
my-app/
  README.md
  node_modules/
  package.json
  .gitignore
  public/
    favicon.ico
    index.html
    manifest.json
  src/
    App.css
    App.js
    App.test.js
    index.css
    index.js
    logo.svg

manifest.json 指定了开始页面 index.html,一切的开始都从这里开始,所以这个是代码执行的源头。

尝试修改 src/App.js 文件代码:

src/App.js

代码语言:javascript复制
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
 
class App extends Component {
  render() {
    return (
      <div className="App">
        <div className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h2>Hello World!</h2>
        </div>
        <p className="App-intro">
          你可以在 <code>src/App.js</code> 文件中修改。
        </p>
      </div>
    );
  }
}
 
export default App;

修改后,打开 http://localhost:3000/ (一般自动刷新),就可以看到输出效果。

src/index.js 是一个入口文件,我们可以尝试直接修改 src/index.js 文件代码:

代码语言:javascript复制
import React from 'react';
import ReactDOM from 'react-dom';

function Hello(props) {
  return <h1>Hello World!</h1>;
}

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

这时候浏览器打开 http://localhost:3000/ 就会输出:

代码语言:javascript复制
Hello World!

3. React 元素渲染

元素是构成React应用的最小单位,它用于描述屏幕上输出的内容。

代码语言:javascript复制
const element = <h1>Hello, world!</h1>;

与浏览器的DOM元素不同,React当中的元素事实上是普通的对象,React DOM可以确保浏览器DOM的数据内容与React元素保持一致。

3.1 将元素渲染到DOM中

首先我们在一个HTML页面中添加一个id="example"<div>

代码语言:javascript复制
<div id="example"></div>

在此div中的所有内容都将由React DOM来管理,所以我们将其称为“根”DOM节点。

我们用React开发应用时一般只会定义一个根节点。但如果你是在一个已有的项目当中引入React的话,你可能会需要在不同的部分单独定义React根节点。

要将React元素渲染到根DOM节点中,我们通过把它们都传递给ReactDOM.render()的方法来将其渲染到页面上:

代码语言:javascript复制
const element = <h1>Hello, world!</h1>
ReactDOM.render(
	element,
    document.getElementById("example")
)

3.2 更新元素渲染

React元素都是不可变的。当元素被创建之后,你是无法改变其内容或属性的。

目前更新界面的唯一办法是创建一个新的元素,然后将它传入ReactDOM.render()方法:

下面是一个计时器的例子:

代码语言:javascript复制
function tick(){
	const element = (
		<div>
			<h1>Hello, world!</h1>
			<h2>现在是{new Date().toLocaleTimeString()}.</h2>
		</div>
	);
				
	ReactDOM.render(
		element,
		document.getElementById("example")
	);
}
setInterval(tick, 1000);

以上实例通过setInterval()方法,每秒钟调用一次ReactDOM.render()。

我们可以将要展示的部分封装起来,以下实例用一个函数来表示:

代码语言:javascript复制
function Clock(props){
    return (
        <div>
            <h1>Hello, world!</h1>
            <h2>现在是{props.date.toLocaleTimeString()}.</h2>
        </div>
    );
}
function tick(){
    ReactDOM.render(
        <Clock date={new Date()} />,
        document.getElementById("example")
    );
}

setInterval(tick, 1000);

除了函数外,我们还可以创建一个React.Component的ES6类,该类封装了要展示的元素,需要注意的是在render()方法中,需要使用this.props替换props:

代码语言:javascript复制
class Clock extends React.Component{
    render(){
        return (
            <div>
                <h1>Hello, world!</h1>
                <h2>现在是 {this.props.date.toLocaleTimeString()}.</h2>
            </div>
        );
    }
}
function tick(){
    ReactDOM.render(
        <Clock date={new Date()} />,
        document.getElementById("example")
    );
}
setInterval(tick, 1000)

4. React JSX

React使用JSX来替代常规的JavaScript。

JSX是一个看起来很像XML的JavaScript语法扩展。

我们不要一定使用JSX,但它有以下优点:

  • JSX执行更快,因为它在编译为JavaScript代码后进行了优化。
  • 它是类型安全的,在编译过程中就能发现错误。
  • 使用JSX编写模板更加简单快速。

可以先看以下代码:

代码语言:javascript复制
const element = <h1>Hello, world!</h1>;

这种看起来可能有些奇怪的标签语法既不是字符串也不是HTML。

它被称为JSX,一种JavaScript的语法扩展。推荐在React使用JSX来描述用户界面。

JSX是在JavaScript内部实现的。

我们知道元素是构成React应用的最小单位,JSX就是用来声明React当中的元素。

与浏览器的DOM元素不同,React当中的元素事实上是普通的对象,React DOM可以确保浏览器DOM的数据内容与React元素保持一致。

要将React元素渲染到根DOM节点中,我们通过把它们都传递给ReactDOM.render()的方法来将其渲染到页面上:

代码语言:javascript复制
var myDivElement = <div className="foo" />;
ReactDOM.render(myDivElement, document.getElementById('example'));

注意:
由于JSX就是JavaScript,一些标识符像class和for不建议作为XML属性名。
作为代替,React DOM使用className和htmlFor来做对应的属性。

4.1 使用JSX

JSX看起来类似于HTML,看以下实例:

代码语言:javascript复制
ReactDOM.render(
	<h1>Hello, world!</h1>,
    document.getElementById("example")
);

可以根据项目需要在以上代码中嵌套多个HTML标签,需要使用一个div元素包裹它,实例中的p元素添加了自定义属性data-myattribute,添加自定义属性需要使用data-前缀。

代码语言:javascript复制
ReactDOM.render(
	<div>
    	<h1>React学习</h1>
        <h2>欢迎学习React</h2>
        <p data-myattribute="somevalue">这是一个很不错的 JavaScript 库!</p>
    </div>,
    document.getElementById("example")
);

4.2 独立文件

你的React JSX代码可以放在一个独立文件上,例如创建一个helloword_react.js文件,代码如下:

代码语言:javascript复制
ReactDOM.render(
	<h1>Hello, world!</h1>,
    document.getElementById("example")
);

然后在HTML文件中引入该JS文件:

代码语言:javascript复制
<body>
    <div id="example"></div>
    <script type="taxt/babel" src="helloworld_react.js"></script>
</body>

4.3 JavaScript表达式

我们可以在JSX中使用JavaScript表达式。表达式写在花括号{}中。实例如下。

代码语言:javascript复制
ReactDOM.render(
    <div>
        <h1>{1 1}</h1>
    </div>,
    document.getElementById("example")
);

JSX中不能使用if else 语句,但是可以使用conditional(三元运算)表达式来代替。以下实例中如果变量i等于1,浏览器将输出true,如果修改i的值,则会输出false

代码语言:javascript复制
const i = 1
ReactDOM.render(
    <div>
        <h1>{i == 1 ? 'True!' : 'False!'}</h1>
    </div>,
    document.getElementById("example")
);

4.4 样式

React推荐使用内联样式。我们可以使用camelCase语法来设置内联样式。React会在指定元素数字后自动添加px。以下实例演示了为h1元素添加myStyle内联样式。

代码语言:javascript复制
var myStyle = {
    fontSize: 100,
    color: '#FF0000'
};
ReactDOM.render(
    <h1 style={myStyle}>静候佳音</h1>,
    document.getElementById("example")
);

4.5 注释

注释需要写在花括号中,实例如下:

代码语言:javascript复制
ReactDOM.render(
    <div>
        <h1 style={myStyle}>静候佳音</h1>
        {/*注释.....*/}
    </div>,
    document.getElementById("example")
);

4.6 数组

JSX允许在模板中插入数组,数组会自动展开所有成员:

代码语言:javascript复制
var arr = [
    <h1>静候佳音</h1>,
    <h2>欢迎学习React!</h2>,
]
ReactDOM.render(
    <div>{arr}</div>,
    document.getElementById("example")
);

5. React组件

如何使用组件使得我们的应用更容易管理。

接下来封装一个输出"Hello World!"的组件,组件名为HelloMessage:

代码语言:javascript复制
function HelloMessage(props){
    return <h1>Hello World!</h1>;
}

const element = <HelloMessage />;

ReactDOM.render(
    element,
    document.getElementById("example")
);

5.1 实例解析

我们使用函数定义了一个组件:

代码语言:javascript复制
function HelloMessage(props){
    return <h1>Hello World!</h1>;
}

也可以用ES6 class来定义一个组件:

代码语言:javascript复制
class WelCome extends React.Component {
    render() {
        return <h1>Hello World!</h1>;
    }
}

const elemnt = <HelloMessage />为用户自定义的组件。

注意:原生HTML元素名以小写字母开头,而自定义的React类名以大写字母开头,比如HelloMessage不能写成helloMessage。除此之外还需要注意组件类只能包含一个顶层标签,否则也会报错。

如果我们需要向组件传递参数,可以使用this.props对象,实例如下:

代码语言:javascript复制
function HelloMessage(props) {
    return <h1>Hello {props.name}!</h1>;
}
 
const element = <HelloMessage name="JiaYin"/>;
 
ReactDOM.render(
    element,
    document.getElementById('example')
);

以上实例中name属性通过props.name来获取。

代码语言:javascript复制
注意:在添加属性时,class属性需要写成className,for属性需要写成htmlFor,这是因为class和for是JavaScript的保留字。

5.2 复合组件

我们可以通过创建多个组件来合成一个组件,即把组件的不用功能点进行分离。

以下实例我们实现了输出网站名字和网址的组件:

代码语言:javascript复制
function Name(props){
    return <h1>网站名称:{props.name}</h1>;
}

function Url(props){
    return <h1>网站地址:{props.url}</h1>;
}

function Nickname(props){
    return <h1>网站小名:{props.nickname}</h1>;
}

function App(){
    return (
        <div>
            <Name name = "Github" />
            <Url url = "https://github.com" />
            <Nickname nickname = "gayhub" />
        </div>
    )
}

ReactDOM.render(
    <App />,
    document.getElementById("example")
);

实例中App组件使用了Name、Url和Nickname组件来输出对应的信息。

6. React State(状态)

React把组件看成是一个状态机(State Machines)。通过与用户的交互,实现不同的状态,然后渲染UI,让用户界面和数据保持一致。

React里,只需要更新组件的State,然后根据新的State重新渲染用户界面(不需要操作DOM)。

以下实例创建一个名称扩展为React Component的ES6类,在render()方法中使用this.state来修改当前的时间。

添加一个类构造函数来初始化状态this.state,类组件应该始终使用props调用基础构造函数。

代码语言:javascript复制
class Clock extends React.Component{
    constructor(props){
        super(props);
        this.state = {date: new Date()};
    }

    render(){
        return (
            <div>
                <h1>Hello, world!</h1>
                <h2>现在是{this.state.date.toLocaleTimeString()}.</h2>
            </div>
        )
    }
}
ReactDOM.render(
    <Clock />,
    document.getElementById('example')
);

接下来,我们将是Clock设置自己的计时器并每秒更新一次。

6.1 将生命周期方法添加到类中

在具有许多组件的应用程序中,在销毁时释放组件所占用的资源非常重要。

每当Clock组件第一次加载到DOM中的时候,我们都想生成定时器,这在React中被称为挂载。

同样,每当Clock生成的这个DOM被移除的时候,我们也会想要清除定时器,这在React中被称为卸载。

我们可以在组件类上声明特殊的方法,当组件挂载或卸载时,来运行一些代码:

代码语言:javascript复制
class Clock extends React.Component{
    constructor(props){
        super(props);
        this.state = {date: new Date()};
    }

    componentDidMount(){
        this.timerID = setInterval(
            () => this.tick(),
            1000
        );
    }

    componentWillUnmount() {
        clearInterval(this.timerID);
    }

    tick(){
        this.setState({
            date: new Date()
        })
    }
    render(){
        return (
            <div>
                <h1>Hello, world!</h1>
                <h2>现在是{this.state.date.toLocaleTimeString()}.</h2>
            </div>
        )
    }
}
ReactDOM.render(
    <Clock />,
    document.getElementById('example')
);

实例解析:

componentDidMount()与componentWillUnmount()方法被称为生命周期钩子。

在组件输出到DOM后会执行componentDidMount()钩子,我们就可以在这个钩子上设置一个定时器。

this.timeID为定时器的ID,我们可以在componentWillUnmount()钩子中卸载定时器。

代码执行顺序:

  1. 当<Clock />被传递给ReactDOM.render()时,React调用Clock组件的构造函数。由于Clock需要显示当前时间,所以使用包含当前时间的对象来初始化this.state。我们稍后会更新此状态。
  2. React然后调用Clock组件的render()方法。这是React了解屏幕上应该显示什么内容,然后React更新DOM以匹配Clock的渲染输出。
  3. 当Clock的输出插入到DOM时,React调用componentDidMount()生命周期钩子。在其中,Clock组件要求浏览器设置一个定时器,每秒钟调用一次tick()。
  4. 浏览器每秒钟调用tick()方法。在其中,Clock组件通过使用包含当前时间的对象调用setState()来调度UI更新。通过调用setState(),React知道状态已经改变,并再次调用render()方法来确定屏幕上应该显示什么。这一次,render()方法中的this.state.date将不同,所以渲染输出将包含更新的时间,并相应地更新DOM。
  5. 一旦Clock组件将被DOM中移除,React会调用componentWillUnmount()这个钩子函数,定时器也会被清除。

6.2 数据自订向下流动

父组件或子组件都不能知道某个组件是有状态还是无状态,并且它们不应该关心某组件是被定义为一个函数还是一个类。

这就是为什么状态通常被称为局部或封装。除了拥有并设置它的组件外,其它组件不可访问。

以下实例中FormattedDate组件将其属性中接收到date值,并且把不知道它是来自Clock状态、还是来自Clock的属性、亦或手工输入:

代码语言:javascript复制
function FormattedDate(props) {
    return <h2>现在是{props.date.toLocaleTimeString()}.</h2>;
}

class Clock extends React.Component {
    constructor(props) {
        super(props);
        this.state = {date: new Date()};
    }

    componentDidMount() {
        this.timerID = setInterval(() => this.tick(), 1000);
    }

    componnetWillUnmount() {
        clearInterval(this.timerID);
    }

    tick() {
        this.setState({
            date: new Date()
        });
    }

    render() {
        return (
            <div>
                <h1>Hello, world!</h1>
                <FormattedDate date = {this.state.date} />
            </div>
        );
    }
}

ReactDOM.render(
    <Clock />,
    document.getElementById('example')
);

这通常被称为自顶向下或单向数据流。任何状态始终由某些特定组件所有,并且从该状态导出的任何数据或UI只能影响树中下方的组件。

如果你能想象一个组件树作为属性的瀑布,每个组件的状态就像一个额外的水源,它连接在一个任意点,但也留下来。

为了表明所有组件都是真正隔离的,我们可以创建一个App组件,它渲染三个Clock

代码语言:javascript复制
function FormattedDate(props) {
    return <h2>现在是{props.date.toLocaleTimeString()}.</h2>;
}

class Clock extends React.Component {
    constructor(props) {
        super(props);
        this.state = {date: new Date()};
    }

    componentDidMount() {
        this.timerID = setInterval(() => this.tick(), 1000);
    }

    componnetWillUnmount() {
        clearInterval(this.timerID);
    }

    tick() {
        this.setState({
            date: new Date()
        });
    }

    render() {
        return (
            <div>
                <h1>Hello, world!</h1>
                <FormattedDate date = {this.state.date} />
            </div>
        );
    }
}

function App() {
    return (
        <div>
            <Clock />
            <Clock />
            <Clock />
        </div>
    )
}
ReactDOM.render(
    <App />,
    document.getElementById('example')
);

以上实例中每个Clock组件都建立了自己的定时器并且独立更新。

React应用程序中,组件是有状态还是无状态被认为是可能随时间而变化的组件的实现细节。

我们可以在有状态组件中使用无状态组件,也可以在无状态组件中使用有状态组件。

7. React Props

stateprops主要的区别在于props是不可变的,而state可以根据与用户交互来改变。这就是为什么有些容器组件需要定义state来更新和修改数据。而子组件只能通过props来传递数据。

7.1 使用props

以下实例演示了如何在组件中使用props:

代码语言:javascript复制

function HelloMessage(props){
    return <h1>Hello {props.name}!</h1>;
}

const element = <HelloMessage name = "JiaYin" />

ReactDOM.render(
    element,
    document.getElementById("example")
);

例中name属性通过props.name来获取。

7.2 默认props

你可以通过组件类的defaultProps属性为props设置默认值,实例如下:

代码语言:javascript复制

class HelloMessage extends React.Component {
    render() {
        return (
            <h1>Hello, {this.props.name}!</h1>
        );
    }
}

HelloMessage.defaultProps = {
    name: "Jiayin"
};

const element = <HelloMessage />

ReactDOM.render(
    element,
    document.getElementById("example")
);

7.3 State和Props

以下实例演示了如何在应用中结合使用stateprops。我们可以在父组件中设置state,并通过在子组件上使用props将其传递到子组件上。在render函数中,我们设置namesite来获取父组件传递过来的数据。

代码语言:javascript复制

class WebSite extends React.Component {
    constructor() {
        super();

        this.state = {
            name: '静候佳音',
            site: 'https://github.com/jiayin-wait'
        }
    }
    render() {
        return (
            <div>
                <Name name = {this.state.name} />
                <Link site = {this.state.site} />
            </div>
        );
    }

}

class Name extends React.Component {
    render() {
        return (
            <h1>{this.props.name}</h1>
        );
    }
}
class Link extends React.Component {
    render() {
        return (
            <a href={this.props.site}>
                {this.props.site}
            </a>
        );
    }
}
ReactDOM.render(
    <WebSite />,
    document.getElementById('example')
);

7.4 Props验证

React.PropTypes在React v15.6版本后已经移到了prop-types库。

代码语言:javascript复制
<script src="https://cdn.bootcss.com/prop-types/15.6.1/prop-types.js"></script>

Props验证使用propTypes,它可以保证我们的应用组件被正确使用,React.PropTypes提供很多验证器(validator)来验证传入数据是否有效。当向props传入无效数据是,JavaScript控制台会抛出警告。

以下实例创建一个MyTitle组件,属性title是必须的且是字符串,非字符串类型会自动转换为字符串:

代码语言:javascript复制


var title = "标题1"
// var title = 123
class MyTitle extends React.Component {    
    render(){
        return (
            <h1>Hello, {this.props.title}</h1>
        );
    }
}
MyTitle.propTypes = {
    title: PropTypes.string
};
ReactDOM.render(
    <MyTitle title = {title} />,
    document.getElementById('example')
);

代码语言:javascript复制

var title = "标题1"
// var title = 123
class MyTitle extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.title}</h1>
    );
  }
}

MyTitle.propTypes = {
  title: PropTypes.string.isRequired
};
ReactDOM.render(
    <MyTitle title = {title} />,
    document.getElementById('example')
);

更多验证器说明如下:

代码语言:javascript复制
MyComponent.propTypes = {
    // 可以声明 prop 为指定的 JS 基本数据类型,默认情况,这些数据是可选的
       optionalArray: React.PropTypes.array,
    optionalBool: React.PropTypes.bool,
    optionalFunc: React.PropTypes.func,
    optionalNumber: React.PropTypes.number,
    optionalObject: React.PropTypes.object,
    optionalString: React.PropTypes.string,
 
    // 可以被渲染的对象 numbers, strings, elements 或 array
    optionalNode: React.PropTypes.node,
 
    //  React 元素
    optionalElement: React.PropTypes.element,
 
    // 用 JS 的 instanceof 操作符声明 prop 为类的实例。
    optionalMessage: React.PropTypes.instanceOf(Message),
 
    // 用 enum 来限制 prop 只接受指定的值。
    optionalEnum: React.PropTypes.oneOf(['News', 'Photos']),
 
    // 可以是多个对象类型中的一个
    optionalUnion: React.PropTypes.oneOfType([
      React.PropTypes.string,
      React.PropTypes.number,
      React.PropTypes.instanceOf(Message)
    ]),
 
    // 指定类型组成的数组
    optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number),
 
    // 指定类型的属性构成的对象
    optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number),
 
    // 特定 shape 参数的对象
    optionalObjectWithShape: React.PropTypes.shape({
      color: React.PropTypes.string,
      fontSize: React.PropTypes.number
    }),
 
    // 任意类型加上 `isRequired` 来使 prop 不可空。
    requiredFunc: React.PropTypes.func.isRequired,
 
    // 不可空的任意类型
    requiredAny: React.PropTypes.any.isRequired,
 
    // 自定义验证器。如果验证失败需要返回一个 Error 对象。不要直接使用 `console.warn` 或抛异常,因为这样 `oneOfType` 会失效。
    customProp: function(props, propName, componentName) {
      if (!/matchme/.test(props[propName])) {
        return new Error('Validation failed!');
      }
    }
  }
}

8. React事件处理

React元素的事件处理和DOM元素类似。但是有一点语法上的不同:

  • React事件绑定属性的命名采用驼峰式写法,而不是小写。
  • 如果采用JSX的语法你需要传入一个函数作为事件处理函数,而不是一个字符串(DOM元素的写法)

HTML通常写法是:

代码语言:javascript复制
<button onclick="activateLasers()">
    激活按钮
</button>

React中的写法为:

代码语言:javascript复制
<button onClick={activateLasers}>
    激活按钮
</button>

React中另一个不同是你不能使用false的方式阻止默认行为,你必须明确使用preventDefault

例如,我们通常在HTML中阻止链接默认打开一个新页面,可以这样写:

代码语言:javascript复制
<a href="#" onclick="console.log('点击链接'); return false">
    点我
</a>

React中的写法为:

代码语言:javascript复制

function ActionLink() {
    function handleClick(e) {
        e.preventDefault();
        console.log('链接被点击');
    }

    return (
        <a href="#" onClick={handleClick}>
            点我
        </a>
    )
}
ReactDOM.render(
    <ActionLink />,
    document.getElementById('example')
);

实例中e是一个合成事件。

使用React的时候通常你不需要使用addEventListener为一个已创建的DOM元素添加监听器。你仅仅需要在这个元素初始渲染的时候提供一个监听器。

当你使用ES6 class语法来定义一个组件的时候,事件处理器会成为类的一个方法。例如,下面的Toggle组件渲染一个让用户切换开关状态的按钮:

代码语言:javascript复制

class Toggle extends React.Component {
    constructor(props) {
        super(props);
        this.state = {isToggleOn: true};

        // 这边绑定是必要的,这样this才能在回调函数中使用
        this.handleClick = this.handleClick.bind(this);
    }

    handleClick() {
        this.setState(prevState => ({
            isToggleOn: !prevState.isToggleOn
        }));
    }

    render() {
        return (
            <button onClick = {this.handleClick}>
                {this.state.isToggleOn ? 'ON' : 'OFF'}
            </button>
        );
    }
}
ReactDOM.render(
    <Toggle />,
    document.getElementById('example')
);
    

你必须谨慎对待JSX回调函数中的this,类的方法默认是不会绑定this的。如果你忘记绑定this.handleClick并把它传入onClick,当你调用这个函数的时候this的值会是undefined

这并不是React的特殊行为;它是函数如何在JavaScript中运行的一部分。通常情况下,如果你没有在方法后面添加( ),例如onClick={this.handleClick},你应该为这个方法绑定this

代码语言:javascript复制
如果使用bind让你很烦,这里有两种方式可以解决。如果你正在实验性的属性初始化器语法,你可以使用属性初始化器来正确的绑定回调函数:
class LoggingButton extends React.Component {
    // 这个语法确保了this绑定在handleClick中
    // 这里只是一个测试
    handleClick = () =>{
        console.log('this is: ', this)
    }

    render() {
        return (
            <button onClick = {this.handleClick}>
                Click me!
            </button>
        );
    }
}
ReactDOM.render(
    <LoggingButton />,
    document.getElementById('example')
);

如果你没有使用属性初始化器语法,你可以在回调函数中使用箭头函数:

代码语言:javascript复制
class LoggingButton extends React.Component {
    handleClick() {
        console.log('this is: ', this)
    }

    render() {
        // 这个语法确保了this绑定在handleClick中
        return (
            <button onClick = {(e)=> this.handleClick(e)}>
                Click me!
            </button>
        );
    }
}
ReactDOM.render(
    <LoggingButton />,
    document.getElementById('example')
);

使用这个语法有个问题就是每次LoggingButton渲染的时候都会创建一个不同的回调函数。在大多数情况下,这没有问题。然而如果这个回调函数作为一个属性值传入低阶组件,这些组件可能会进行额外的重新渲染,我们通常建议在构造函数中绑定或使用属性初始化器语法来避免这类性能问题。

8.1 向事件处理程序传递参数

通常我们会为事件处理程序传递额外的参数,例如,若是id是你要删除那一行id,以下两种方式都可以向事件处理程序传递参数:

代码语言:javascript复制
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.binnd(this, id)}>Delete Row</button>

上述两种方式是等价的。

上面两个例子中,参数e作为React事件对象将会被作为第二个参数进行传递。通过箭头函数的方式,事件对象必须是显示的进行传递,但是通过bind的方式,事件对象以及更多的参数将会被隐式的进行传递。

值得注意的是,通过bind方式向监听函数传参,在类组件中定义的监听函数,事件对象e要排在所传递参数的后面,例如:

代码语言:javascript复制

class Popper extends React.Component {
    constructor(){
        super();
        this.state = {name: 'Hello world!'};
    }

    preventPop(name, e){   // 事件对象e要放在最后
        e.preventDefault();
        alert(name);
    }
    render() {
        return (
            <div>
                <p>hello</p>
                {/* 通过bind() 方法传递参数。*/}
                <a href="https://reactjs.org" onClick={this.preventPop.bind(this, this.state.name)}>Click</a>
            </div>
        );
    }
}
ReactDOM.render(
    <Popper />,
    document.getElementById('example')
);

9. React 条件渲染

React中,你可以创建不同的组件来封装各种你需要的行为。然后还可以根据应用的状态变化只渲染其中的一部分。

React中的条件渲染和JavaScript中的一致,使用JavaScript操作符if或条件运算符来创建表示当前状态的元素,然后让React根据它们来更新UI

先来看两个组件:

代码语言:javascript复制

function UserGreeting(props) {
    return <h1>欢迎回来!</h1>;
}

function GuestGreeting(props) {
    return <h1>请先注册.</h1>;
}

我们将创建一个Greeting组件,它会根据用户是否登录来显示其中之一:

代码语言:javascript复制

function UserGreeting(props) {
    return <h1>欢迎回来!</h1>;
}

function GuestGreeting(props) {
    return <h1>请先注册.</h1>;
}
function Greeating(props) {
    const isLoggedIn = props.isLoggedIn;
    if(isLoggedIn){
        return <UserGreeting />
    }
    return <GuestGreeting />
}
ReactDOM.render(
    // 尝试修改isLoggedIn={true}:
    <Greeating isLoggedIn = {false} />,
    document.getElementById('example')
);

9.1 元素变量

你可以使用变量来储存元素。它可以帮助你有条件的渲染组件的一部分,而输出的其它部分不会更改。

在下面的例子中,我们将要创建一个名为LoginControl的有状态组件。

它会根据当前的状态来渲染<LoginButton /><LogoutButton />,它也将渲染前面例子中的<Greeting />

代码语言:javascript复制
class LoginControl extends React.Component {
    constructor(props){
        super(props);
        this.handleLoginClick = this.handleLoginClick.bind(this);
        this.handleLogoutClick = this.handleLogoutClick.bind(this);
        this.state = {isLoggedIn: false};
    }

    handleLoginClick() {
        this.setState({isLoggedIn: true});
    }
    handleLogoutClick() {
        this.setState({isLoggedIn: false});
    }

    render() {
        const isLoggedIn = this.state.isLoggedIn;

        let button = null;
        if(isLoggedIn){
            button = <LogoutButton onClick = {this.handleLogoutClick} />;
        } else {
            button = <LoginButton onClick = {this.handleLoginClick} />;
        }

        return (
            <div>
                <Greeting isLoggedIn = {isLoggedIn} />
                {button}
            </div>
        )
    }
}

function UserGreeting(props) {
    return <h1>欢迎回来!</h1>;
}

function GuestGreeting(props) {
    return <h1>请先注册.</h1>;
}
function Greeting(props) {
    const isLoggedIn = props.isLoggedIn;
    if(isLoggedIn){
        return <UserGreeting />
    }
    return <GuestGreeting />
}
function LoginButton(props) {
  return (
    <button onClick={props.onClick}>
      登陆
    </button>
  );
}

function LogoutButton(props) {
  return (
    <button onClick={props.onClick}>
      退出
    </button>
  );
}
ReactDOM.render(
    <LoginControl />,
    document.getElementById('example')
);

9.2 与运算符&&

你可以通过花括号包裹代码在JSX中嵌入任何表达式,也包括JavaScript的逻辑与&&,它可以方便地条件渲染一个元素。

代码语言:javascript复制
function MailBox(props){
    const unreadMessages = props.unreadMessages;
    return (
        <div>
            <h1>Hello!</h1>
            {unreadMessages.length > 0 &&
                <h2>
                    您有 {unreadMessages.length} 条未读信息。
                </h2>
            }
        </div>
    )
}
const messages = ['React', 'Re: React', 'Re: Re: React'];
ReactDOM.render(
    <MailBox unreadMessages = {messages} />,
    document.getElementById('example')
)

JavaScript中,true && expression 总是返回 expression, 而 false && expression总是返回false

因此,如果条件是true&&右侧的元素就会被渲染,如果是falseReact会忽略并跳过它。

9.3 三目运算符

条件渲染的另一种方法是使用JavaScript的条件运算符。

代码语言:javascript复制
condition ? true : false

在下面的例子中,我们用它来有条件的渲染一小段文本。

代码语言:javascript复制
render(){
    const isLoggedIn = this.state.isLoggedIn;
    return (The User is {isLoggedIn ? 'currently' : 'not'} logged in.);
}

同样,它也可以用在较大的表达式中,虽然不太直观:

代码语言:javascript复制
render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      {isLoggedIn ? (
        <LogoutButton onClick={this.handleLogoutClick} />
      ) : (
        <LoginButton onClick={this.handleLoginClick} />
      )}
    </div>
  );
  }

9.4 阻止组件渲染

在极少数情况下,你可能希望隐藏组件,即使它被其它组件渲染。让render方法返回null而不是它的渲染结果即可实现,在下面的例子中,<WarningBanner />根据属性warn的值条件渲染。如果warn的值是false,则组件不会渲染:

代码语言:javascript复制

function WarningBanner(props){
    if(!props.warn){
        return null;
    }
    return (
        <div classaName="warning">
            警告!
        </div>
    )
}
class Page extends React.Component {
    constructor(props) {
        super(props);
        this.state = {showWarning: true}
        this.handleToggleClick = this.handleToggleClick.bind(this);
    }

    handleToggleClick() {
        this.setState(prevState => ({
            showWarning: !prevState.showWarning
        }));
    }
    render(){
        return (
            <div>
                <WarningBanner warn={this.state.showWarning} />
                <button onClick={this.handleToggleClick}>
                    {this.state.showWarning ? '隐藏' : '显示'}
                </button>
            </div>
        )
    }
}

ReactDOM.render(
    <Page />,
    document.getElementById("example")
)

组件的render方法返回null并不会影响该组件生命周期方法的回调。例如,componentWillUpdatecomponentDidUpdate依然可以被调用。

10. React列表&key

我们可以使用JavaScriptmap()方法来创建列表。

使用map()方法遍历数组生成了一个1到5的数字列表。

代码语言:javascript复制

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((numbers) =>
    <li>{numbers}</li>
);
ReactDOM.render(
    <ul>{listItems}</ul>,
    document.getElementById('example')
)

我们可以将以上实例重构成一个组件,组件接收数组参数,每个列表元素分配一个 key,不然会出现警告 a key should be provided for list items,意思就是需要包含 key

代码语言:javascript复制

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    <li key={number.toString()}>
      {number}
    </li>
  );
  return (
    <ul>{listItems}</ul>
  );
}
 
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('example')
);

10.1 Keys

Keys可以在DOM中的某些元素被增加或删除的时候帮助React识别哪些元素发生了变化。因此你应当给数组中的每一个元素赋予一个确定的标识。

代码语言:javascript复制
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) => 
    <li key={number.toString()}>
        {number}
    </li>
)

一个元素的key最好是这个元素在列表中拥有的一个独一无二的字符串。通常,我们使用来自数据的id作为元素的key

代码语言:javascript复制
const todoItems = todos.map((todo) => 
    <li key=todo.id>
        {todo.text}
    </li>                           
)

当元素没有确定的id时,你可以使用它的序列号索引index作为key

代码语言:javascript复制
const todoItems - todos.map((todo, index) =>
    // 只有在没有确定的id时使用
    <li key={index}>
        {todo.text}
    </li>
)

如果列表可以重新排序,我们不建议使用索引来进行排序,因为这会导致渲染变得很慢。

10.2 用keys提取组件

元素的key只有在它和它的兄弟节点对比时才有意义。

比方说,如果你提取出一个ListItem组件,你应该把key保存在数组中的这个<ListItem />元素上,而不是放在ListItem组件中的<li>元素上。

10.2.1 错误的示范

代码语言:javascript复制
function ListItem(props) {
  const value = props.value;
  return (
    // 错啦!你不需要在这里指定key:
    <li key={value.toString()}>
      {value}
    </li>
  );
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    //错啦!元素的key应该在这里指定:
    <ListItem value={number} />
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('example')
);

10.2.2 key的正确使用方式

代码语言:javascript复制
function ListItem(props) {
  // 对啦!这里不需要指定key:
  return <li>{props.value}</li>;
}
 
function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // 又对啦!key应该在数组的上下文中被指定
    <ListItem key={number.toString()}
              value={number} />
 
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}
 
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('example')
);

当你在map()方法的内部调用元素时,你最好随时记得为每一个元素加上一个独一无二的key

10.3 元素的key在它的兄弟元素之间应该唯一

数组元素中使用的key在其兄弟之间应该是独一无二的。然而,它们不需要是全局唯一的。当我们生成两个不同的数组时,我们可以使用相同的键。

代码语言:javascript复制

function Blog(props){
    const sidebar = (
        <ul>
            {props.posts.map((post) =>
                <li key={post.id}>
                    {post.title}
                </li>
            )}
        </ul>
    );
    const content = props.posts.map((post) =>
        <div key={post.id}>
            <h3>{post.title}</h3>
            <p>{post.content}</p>
        </div>
    );
    return (
        <div>
            {sidebar}
            <hr />
            {content}
        </div>
    )
}
const posts = [
    {id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
    {id: 2, title: 'Installation', content: 'You can install React from npm.'}
]
ReactDOM.render(
    <Blog posts={posts} />,
    document.getElementById('example')
)
代码语言:javascript复制
key会作为React的提示,但不会传递给你的组件。如果你的组件中需要使用和key相同的值,请将其作为属性传递:
const content = posts.map((post) => 
    <Post 
        key={post.id}
        id={post.id}
        title={post.title}
           />
)

上面的demo中,Post组件可以读出props.id,但是不能读出props.key

10.4 在jsx中嵌入map()

在上面的demo中,我们声明了一个单独的listItems变量并将其包含在JSX中。

代码语言:javascript复制
function ListItem(props) {
  return <li>{props.value}</li>;
}
function NumberList(props){
    const numbers = props.numbers;
    const listItems = numbers.map((number) => 
        <ListItem key={number.toString()}
            value={number} />
    )
    return (
        <ul>
            {listItems}
        </ul>
    )
}
const numbers = [1, 2, 3, 4, 5, 6]
ReactDOM.render(
    <NumberList numbers = {numbers} />,
    document.getElementById('example')
)

JSX允许在大括号中嵌入任何表达式,所以我们可以在map()中这样使用:

代码语言:javascript复制

function NumberList(props){
    const numbers = props.numbers;
    return (
        <ul>
            {numbers.map((number) =>
                <ListItem key={number.toString()}
                    value={number} />
            )}
        </ul>
    )
}

这么做有时可以使你的代码更清晰,但有时这种风格也会被滥用。就像在JavaScript中一样,何时需要为了可读性提取出一个变量,这完全取决于你。但请记住,如果一个map()嵌套了太多层级,那你就可以提取出组件。

11. React组件API

  • 设置状态:setState
  • 替换状态:replaceState
  • 设置属性:setProps
  • 替换属性:replaceProps
  • 强制更新:forceUpdate
  • 获取DOM节点:findDOMNode
  • 判断组件挂载状态:isMounted

11.1 设置状态:setState

代码语言:javascript复制
setState(object nextState[, function callback])

11.1.1 参数说明

  • nextState:将要设置的新状态,该状态会和当前的state合并
  • callback:可选参数,回调函数。该函数会在setState设置成功,且组件重新渲染后调用。

合并nextState和当前state,并重新渲染组件。setStateReact事件处理函数中和请求回调函数中触发UI更新的主要方法。

11.1.2 关于setState

不能在组件内部通过this.state修改状态,因为该状态会在调用setState()后被替换。

setState()并不会立即改变this.state,而是创建一个即将处理的statesetState()并不一定是同步的,为了提升性能React会批量执行stateDOM渲染。

setState()总是会触发一次组件重绘,除非在shouldComponentUpdate()中实现了一些条件渲染逻辑。

11.1.3 实例

代码语言:javascript复制
class Counter extends React.Component{
    constructor(props) {
        super(props);
        this.state = {clickCount: 0};
        this.handleClick = this.handleClick.bind(this);

    }
    handleClick() {
        this.setState(function(state) {
            return {clickCount: state.clickCount   1};
        })
    }
    render(){
        return (<h2 onClick={this.handleClick}>点我!点击次数为:{this.state.clickCount}</h2>)
    }
}
ReactDOM.render(
    <Counter />,
    document.getElementById('example')
)

实例中通过点击h2标签来使得点击计数器加1。

11.2 替换状态:repalceState

代码语言:javascript复制
replaceState(object nextState[, function callback])

  • nextState:将要设置的新状态,该状态会替换当前的state
  • callback:可选参数,回调函数。该函数会在replaceState设置成功,且组件重新渲染后调用。

replaceState()方法与setState()类似,但是方法只会保留nextState中状态,原state不在nextState中的状态都会被删除。

11.3 设置属性:setProps

代码语言:javascript复制
setProps(object nextProps[, function callback])
  • nextProps:将要设置的新属性,该属性会替换当前的props
  • callback:可选参数,回调函数。该函数会在replaceProps设置成功,且组件重新渲染后调用。

设置组件属性,并重新渲染组件。

props相当于组件的数据流,它总是会从父组件向下传递至所有的子组件中。当和一个外部的JavaScript应用集成时,我们可能会需要向组件传递数据或通知React.render()组件需要重新渲染,可以使用setProps()

更新组件,我们可以在节点上再次调用React.render(),也可以通过setProps()方法改变组件属性,触发组件重新渲染。

11.4 替换属性:replaceProps

代码语言:javascript复制
replaceProps(object nextProps[, function callback])
  • nextProps:将要设置的新属性,该属性会替换当前的props
  • callback:可选参数,回调函数。该函数会在replaceProps设置成功,且组件重新渲染后调用。

replaceProps()方法与setProps类似,但它会删除原有的props

11.5 强制更新:forceUpdate

代码语言:javascript复制
forceUpdate([function callback])

11.5.1 参数说明

  • callback,可选参数,回调函数。该函数会在组件render()方法调用后调用。

forceUpdate()方法会使组件调用自身的render()方法重新渲染组件,组件的子组件也会调用自己的render()。但是,组件重新渲染时,依然会读取this.propsthis.state,如果状态没有改变,那么React只会更新DOM

forceUpdate()方法适用于this.propsthis.state之外的组件重绘(如:修改了this.state后),通过该方法通知React需要调用render(),一般来说,应该尽量避免使用forceUpdate(),而仅从this.propsthis.state中读取状态并由React触发render()调用

11.6 获取DOM节点:findDOMNode

代码语言:javascript复制
DOMElement findDOMNode()

  • 返回值:DOM元素DOMElement

如果组件已经挂载到DOM中,该方法返回对应的本地浏览器DOM元素。当render返回nullfalse时,this.findDOMNode()也会返回null。从DOM中读取值得时候,该方法很有

用,如:获取表单字段的值和做一些DOM操作。

11.7 判断组件挂载状态:isMounted

代码语言:javascript复制
bool isMounted()
  • 返回值:truefalse

isMounted()方法用于判断组件是否已挂载到DOM中。可以使用该方法保证了setState()foorceUpdate()在异步场景下的调用不会出错。

12. React组件生命周期

组件的生命周期可以分为三个状态:

  • Mounting(挂载):已插入真实DOM
  • Updating(更新):正在被重新渲染
  • Unmounting(卸载):已移出真实dom

12.1 挂载

  • constructor():在React组件挂载之前,会调用它的构造函数。
  • getDerivedStateFromProps():在调用render方法之前调用,并且在初始挂载及后续更新时都会被调用。
  • render()render()方法是class组件中唯一必须实现的方法。
  • componentDidMount():在组件挂载后(插入DOM树中)立即调用。

render()方法是class组件中唯一必须实现的方法,其他方法可以根据自己的需要来实现。

12.2 更新

每当组件的stateprops发生变化时,组件就会更新。

当组件的propsstate发生变化时会触发更新。组件更新的生命周期调用顺序如下:

  • getDeriveStateFromProps():在调用render方法之前调用,并且在初始挂载及后续更新时都会被调用。根据shouldComponentUpdate()的返回值,判断React组件的输出是否受当前stateprops更改的影响。
  • shouldComponentUpdate():当propsstate发生变化时,shouldComponentUpdate()会在渲染执行之前被调用。
  • render()render()方法是class组件中唯一必须实现的方法。
  • getSnapshotBeforeUpdate():在最近一次渲染输出(提交到DOM节点)之前调用。
  • componentDidUpdate():在更新后会立即调用。

render()方法是class组件中唯一必须实现的方法,其他方法可以根据自己的需求来实现。

12.3 卸载

当组件从DOM中移除时会调用如下的方法:DOM

  • componentWillUnmount():在组件卸载及销毁之前直接调用。

12.4 实例

以下是一个当前时间的实例,每秒更新:

代码语言:javascript复制

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }
 
  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }
 
  componentWillUnmount() {
    clearInterval(this.timerID);
  }
 
  tick() {
    this.setState({
      date: new Date()
    });
  }
 
  render() {
    return (
      <div>
        <h1>Hello, Runoob!</h1>
        <h2>现在时间是:{this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}
 
ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

以下实例在Hello组件加载以后,通过componentDidMount方法设置一个定时器,每隔100毫秒重新设置组件的透明度,并重新渲染:

代码语言:javascript复制
class Hello extends React.Component {
 
  constructor(props) {
      super(props);
      this.state = {opacity: 1.0};
  }
 
  componentDidMount() {
    this.timer = setInterval(function () {
      var opacity = this.state.opacity;
      opacity -= .05;
      if (opacity < 0.1) {
        opacity = 1.0;
      }
      this.setState({
        opacity: opacity
      });
    }.bind(this), 100);
  }
 
  render () {
    return (
      <div style={{opacity: this.state.opacity}}>
        Hello {this.props.name}
      </div>
    );
  }
}
 
ReactDOM.render(
  <Hello name="world"/>,
  document.body
);

以下实例初始化 statesetNewnumber用于更新 state。所有生命周期在 Content 组件中。

代码语言:javascript复制

class Button extends React.Component {
  constructor(props) {
      super(props);
      this.state = {data: 0};
      this.setNewNumber = this.setNewNumber.bind(this);
  }
  
  setNewNumber() {
    this.setState({data: this.state.data   1})
  }
  render() {
      return (
         <div>
            <button onClick = {this.setNewNumber}>INCREMENT</button>
            <Content myNumber = {this.state.data}></Content>
         </div>
      );
    }
}
 
 
class Content extends React.Component {
  componentWillMount() {
      console.log('Component WILL MOUNT!')
  }
  componentDidMount() {
       console.log('Component DID MOUNT!')
  }
  componentWillReceiveProps(newProps) {
        console.log('Component WILL RECEIVE PROPS!')
  }
  shouldComponentUpdate(newProps, newState) {
        return true;
  }
  componentWillUpdate(nextProps, nextState) {
        console.log('Component WILL UPDATE!');
  }
  componentDidUpdate(prevProps, prevState) {
        console.log('Component DID UPDATE!')
  }
  componentWillUnmount() {
         console.log('Component WILL UNMOUNT!')
  }
 
    render() {
      return (
        <div>
          <h3>{this.props.myNumber}</h3>
        </div>
      );
    }
}
ReactDOM.render(
   <div>
      <Button />
   </div>,
  document.getElementById('example')
);

13. React AJAX

React组件的数据可以通过componentDidMount方法中的Ajax来获取,当从服务端获取数据时可以将数据存储在state中,再用this.setState方法重新渲染UI

当使用异步加载数据时,在组件卸载前使用componentWillUnmount来取消未完成的请求。

以下实例演示了获取Github用户最新gist共享描述:

代码语言:javascript复制

class UserGist extends React.Component {
  constructor(props) {
      super(props);
      this.state = {username: '', lastGistUrl: ''};
  }
 
 
  componentDidMount() {
    this.serverRequest = $.get(this.props.source, function (result) {
      var lastGist = result[0];
      this.setState({
        username: lastGist.owner.login,
        lastGistUrl: lastGist.html_url
      });
    }.bind(this));
  }
 
  componentWillUnmount() {
    this.serverRequest.abort();
  }
 
  render() {
    return (
      <div>
        {this.state.username} 用户最新的 Gist 共享地址:
        <a href={this.state.lastGistUrl}>{this.state.lastGistUrl}</a>
      </div>
    );
  }
}
 
ReactDOM.render(
  <UserGist source="https://api.github.com/users/octocat/gists" />,
  document.getElementById('example')
);

以上代码使用 jQuery 完成 Ajax 请求。

14. React表单与事件

HTML表单元素与React中的其它DOM元素有所不同,因为表单元素生来就保留一些内部状态。

HTML中,像<input /><textarea>,和<select>这类表单元素会维持自身状态,并根据用户输入进行更新。但在React中,可变的状态通常保存在组件的状态属性中,并且只能用setState()方法进行更新。

14.1 一个简单的实例

在实例中我们设置了输入框inputvalue = {this.state.date}。在输入框值发生变化时我们可以更新state。我们可以使用onChange事件来监听input的变化,并修改state

代码语言:javascript复制

class HelloMessage extends React.Component {
  constructor(props) {
      super(props);
      this.state = {value: 'Hello React!'};
      this.handleChange = this.handleChange.bind(this);
  }
 
  handleChange(event) {
    this.setState({value: event.target.value});
  }
  render() {
    var value = this.state.value;
    return <div>
            <input type="text" value={value} onChange={this.handleChange} /> 
            <h4>{value}</h4>
           </div>;
  }
}
ReactDOM.render(
  <HelloMessage />,
  document.getElementById('example')

上面的代码将渲染出一个值为Hello React!input 元素,并通过 onChange 事件响应更新用户输入的值。

14.2 实例2

在以下实例中演示如何在子组件上使用表单。onChange方法将触发state的更新并将更新的值传递到子组件的输入框的value上来重新渲染界面。

需要在父组价通过创建事件句柄(handleChange),并作为prop(updateStateProp)传递到子组件上。

代码语言:javascript复制

class Content extends React.Component {
    render() {
        return <div>
            <input type="text" value={this.props.myDateProp} onChange={this.props.updateStateProp} />
            <h4>{this.props.myDateProp}</h4>
            </div>
    }
}

class HelloMessage extends React.Component {
    constructor(props) {
        super(props);
        this.state = {value: 'Hello React!'};
        this.handleChange = this.handleChange.bind(this);
    }

    handleChange(event) {
        this.setState({value: event.target.value});
    }
    render() {
        var value = this.state.value;
        return <div>
            <Content myDateProp={value} updateStateProp = {this.handleChange}></Content>
            </div>
    }
}
ReactDOM.render(
    <HelloMessage />,
    document.getElementById('example')
)

14.3 Select下拉菜单

React中,不使用selected属性,而在根select标签上用value属性来表示选中项。

代码语言:javascript复制
class FlavorForm extends React.Component {
    constructor(props) {
        super(props);
        this.state = {value: 'coconut'};

        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
    }

    handleChange(event) {
        this.setState({value: event.target.value});
    }
    handleSubmit(event) {
        alert('Your favorite flavor is: '   this.state.value);
        event.preventDefault();
    }
    render() {
        return (
            <form onSubmit={this.handleSubmit}>
                <label>
                    选择您最喜欢的网站
                    <select value={this.state.value} onChange={this.handleChange}>
                        <option value="gg">Google</option>
                        <option value="rn">Runoob</option>
                        <option value="tb">Taobao</option>
                        <option value="fb">FaCEbook</option>
                        <option value="gh">Github</option>
                    </select>
                </label>
                <input type="submit" value="提交" />
            </form>
        )
    }
}
ReactDOM.render(
    <FlavorForm />,
    document.getElementById('example')
)

14.4 多个表单

当有处理多个input元素时,你可以通过给每个元素添加一个name属性,来让处理函数根据event.target.name的值来选择做什么。

代码语言:javascript复制
class Reservation extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            isGoing: true,
            numberOfGuests: 2
        };

        this.handleInputChange = this.handleInputChange.bind(this);
    }

    handleInputChange(event) {
        const target = event.target;
        const value = target.type === 'checkbox' ? target.checked : target.value;
        const name = target.name;

        this.setState({
            [name]: value
        })
    }
    render() {
        return (
            <form onSubmit={this.handleSubmit}>
                <label>
                    是否离开:
                    <input name="isGoing"
                        type="checkbox"
                        checked={this.state.isGoing}
                        onChange={this.handleInputChange} />
                </label>
                <br />
                <label>
                    访客数:
                    <input name="numberOfGuests"
                        type="number"
                        value={this.state.numberOfGuests}
                        onChange={this.handleInputChange} />
                </label>
            </form>
        )
    }
}
ReactDOM.render(
    <Reservation />,
    document.getElementById('example')
)

14.5 React事件

以下实例演示通过onClick事件来修改数据:

代码语言:javascript复制
    constructor(props) {
        super(props);
        this.state = {
            value: 'Hello React!'
        };

        this.handleChange = this.handleChange.bind(this);
    }

    handleChange(event) {
        this.setState({value: '静候佳音!'})
    }
    render() {
        var value = this.state.value;
        return (
            <div>
                <button onClick={this.handleChange}>点我</button>
                <h4>{value}</h4>
            </div>
        )
    }
}
ReactDOM.render(
    <HelloMessage />,
    document.getElementById('example')
)

当你需要从子组件中更新父组件的 state 时,你需要在父组件通过创建事件句柄 (handleChange) ,并作为 prop (updateStateProp) 传递到你的子组件上。实例如下:

代码语言:javascript复制
class Content extends React.Component {
    render() {
        return <div>
                <button onClick={this.props.updateStateProp}>点我</button>
                <h4>{this.props.myDateProp}</h4>
            </div>
    }
}
class HelloMessage extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            value: 'Hello React!'
        };

        this.handleChange = this.handleChange.bind(this);
    }

    handleChange(event) {
        this.setState({value: '静候佳音!'})
    }
    render() {
        var value = this.state.value;
        return (
            <div>
                <Content myDateProp={value} updateStateProp={this.handleChange}></Content>
            </div>
        )
    }
}
ReactDOM.render(
    <HelloMessage />,
    document.getElementById('example')
)

15. React Refs

React支持一种非常特殊的属性Ref,你可以用来绑定到render()输出的任何组件上。

这个特殊的属性允许你引用render()返回的相应的支撑实例(backing instance)。这样就可以确保在任何时间总是拿到正确的实例。

15.1 使用方法

绑定一个ref属性到render的返回值上:

代码语言:javascript复制

<input ref="myInput" />

在其他代码中,通过this.refs来获取支撑实例:

代码语言:javascript复制

var input = this.refs.myInput;
var inputValue = input.value;
var inputRect = input.getBoundingClientRecr();

15.2 完整实例

可以通过使用this来获取当前React组件,或使用ref来获取组价的引用,实例如下:

代码语言:javascript复制

class MyConponent extends React.Component {
    handleClick() {
        // 使用原生的DOM API来获取焦点
        this.refs.myInput.focus();
    }

    render() {
        // 当组件插入到DOM后,ref属性添加一个组件的引用于到this.refs
        return (
            <div>
                <input type="text" ref="myInput" />
                <input
                    type="button"
                    value="点我获取输入框焦点"
                    onClick={this.handleClick.bind(this)}/>
            </div>
        )
    }
}
ReactDOM.render(
    <MyConponent />,
    document.getElementById('example')
)

实例中,我们获取了输入框的支撑实例的引用,子点击按钮后输入框获取焦点。

我们也可我们也可以使用 getDOMNode()方法获取DOM元素


2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!

0 人点赞