react学习

react学习

react 一个UI框架

React用于构建用户界面的 JavaScript 库.

react 特点

  • mvvm
  • 声明式
  • 组件化
  • 生态强大

学习前置基础

  • html\css\js
  • es6+
  • webpack教程
  • nodejs基础|sass|ajax|...

简单html的方式编写react demo快速入门

我们可以直接把React的当做一个JS的库来用(生产环境不要这么用),如下是第一个helloword demo

DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  
  
  <script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js">script>
  
  <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js">script>
  
  <script src="https://unpkg.com/[email protected]/babel.min.js">script>
  <title>aicoder.com  reactdemostitle>
head>
<body>

  
  
  <div id="app">

  div>

  
  
  <script type="text/babel">
    ReactDOM.render(
      <h1>Hello, world! aicoder.com </h1>,
      document.getElementById('app')
    );
  script>
body>
html>

 

ReactDOM.render方法是把JSX语法生成的dom渲染到页面上去。此方法接受两个参数,第一个参数是渲染的html标签,第二个参数是渲染到页面的哪个节点上。

这里牵扯到JSX语法,后续会讲到。

React脚手架创建项目快速入门

快速构建一个React的前端项目最好的就是用脚手架快速生成一个项目架构目录,并做好基础的配置。建议使用Create React App

安装Create React App

# 建议全局安装
$ npm install -g create-react-app

 

# yarn
$ yarn global add create-react-app

 

 

测试是否安装成功:

$ create-react-app -V
2.1.3

 

快速初始化一个react项目

npx create-react-app myapp
cd myapp
npm start

 

 

此时打开http://localhost:3000/就能看到基本的一个简单的web页面。

释放webpack的配置文件

由于create-react-app脚手架生成的项目所有的配置都内置在代码中,我们看不到webpack配置的细节,需要通过一个命令,把所有配置都显示的展现在项目中。

npm run eject

除非您对webpack已经非常熟悉,请不要这么操作!

其他的构建辅助脚本

# 构建项目
npm run build

yarn build

# 运行测试
npm run test
yarn test

# 另外一种构建方式
# required npm 6.0+
npm init react-app my-app

yarn create react-app my-app

 


HelloWorld

项目的默认目录:

├── package.json
├── public                  # 这个是webpack的配置的静态目录
│   ├── favicon.ico
│   ├── index.html          # 默认是单页面应用,这个是最终的html的基础模板
│   └── manifest.json
├── src
│   ├── App.css             # App根组件的css
│   ├── App.js              # App组件代码
│   ├── App.test.js
│   ├── index.css           # 启动文件样式
│   ├── index.js            # 启动的文件(开始执行的入口)!!!!
│   ├── logo.svg
│   └── serviceWorker.js
└── yarn.lock

index.js就是项目启动启动的入口。

import React from 'react';                           // 引入react核心库(必须)
import ReactDOM from 'react-dom';                    // 引入react在浏览器上运行的需要的支持库
import './index.css';
import App from './App';                             // 引入App组件
import * as serviceWorker from './serviceWorker';    // 注册serviceWork

// 此行代码的意思:把App组件的内容经过Ract的编译生成最终的html挂载到 root的dom节点生。
ReactDOM.render(, document.getElementById('root'));  // !!!核心代码

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();

 

核心代码就是下面一行:把App组件的内容经过Ract的编译生成最终的html挂载到 root的dom节点生。

ReactDOM.render(, document.getElementById('root')); // !!!核心代码

 

那么我们看一下App组件的代码:

import React, { Component } from 'react';   // 引入react的组件根对象
import logo from './logo.svg';
import './App.css';

class App extends Component {              // 创建App组件类型,继承Component
  render() {
    return (
      
logo

Edit src/App.js and save to reload.

<a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React
); } } export default App;

 

您需要有es6的语法的基础。

在App.js中就做了以下几件事:

  • 引入React库
  • 定义App类型(继承自React.Component)
  • 在App类中定义render方法
  • 在render方法中返回要渲染的html(jsx语法)

然后我们修改如下App.js为:

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

class App extends Component {
  render() {
    return (
      

Hi, aicoder.com

); } } export default App;

 

此时页面会自动刷新为:

Hi, aicoder.com

JSX语法

JSX, 一种 JavaScript 的语法扩展。JSX 用来声明 React 当中的元素。

比如定义一个变量:

// jsx语法是js和html的组合,本质还是js,最终会编译成js
const element = 

Hello, world!

;

 

JSX 的基本语法规则:遇到 HTML 标签(以 < 开头),就用 HTML 规则解析;遇到代码块(以 { 开头),就用 JavaScript 规则解析。

JSX中使用表达式

如果JSX中的代码超过一行,我们一般用一个()进行分组处理,遇到html一般都会单独写在一个新行。

const element = (
  

Hello, {formatName(user)}!

);

 

再比如:

// 用{}可以直接展示数据内容个,类似es6模板字符串中的 ${}
function getGreeting(user) {
  if (user) {
    return 

Hello, {user}!

; } return

Hello, Stranger.

; }

 

JSX 属性与{}

你可以使用引号来定义以字符串为值的属性:

const element = 
;

 

也可以使用大括号来定义以 JavaScript 表达式为值的属性:

const element = ;

 

JSX 防注入攻击

你可以放心地在 JSX 当中使用用户输入:

const title = 你好!;
// 直接使用是安全的:
const element = 

{title}

;

 

React DOM 在渲染之前默认会 过滤 所有传入的值。它可以确保你的应用不会被注入攻击。所有的内容在渲染之前都被转换成了字符串。这样可以有效地防止 XSS(跨站脚本) 攻击。

数组的展示

变量是一个数组,则会展开这个数组的所有成员。

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

class App extends Component {
  render() {
    var arr = [
      

hi, aicoder.com

,

React is awesome

, ]; return (
{arr}
); } } export default App;

 

最终结果:

hi, aicoder.com
React is awesome

数组map输出一个列表

App.js如下:

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

class App extends Component {
  render() {
    var arr = ['aicoder.com', 'hamkd.com']
    return (
      

aicoder.com

    { arr.map((item, index) =>
  • { index +1 } - { item }
  • ) }
); } } export default App;

 

最终结果:

aicoder.com
1 - aicoder.com
2 - hamkd.com

JSX的最终归宿

JSX 本质会被编译成JS,Babel 转译器会把 JSX 转换成一个名为 React.createElement() 的方法调用。

下面两种代码的作用是完全相同的:

const element = (
  

Hello, world!

); const element = React.createElement( 'h1', {className: 'greeting'}, 'Hello, world!' );

 

React.createElement() 这个方法首先会进行一些避免bug的检查,之后会返回一个类似下面例子的对象:
// 注意: 以下示例是简化过的(不代表在 React 源码中是这样)
const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world'
  }
};
 

这样的对象被称为 “React 元素”。它代表所有你在屏幕上看到的东西。React 通过读取这些对象来构建 DOM 并保持数据内容一致。

React组件

组件可以将UI切分成一些独立的、可复用的部件,这样你就只需专注于构建每一个单独的部件。

组件从概念上看就像是函数,它可以接收任意的输入值(称之为“props”),并返回一个需要在页面上展示的React元素。

函数定义/类定义组件

定义一个组件最简单的方式是使用JavaScript函数:

function Welcome(props) {
  return 

Hello, {props.name}

; }

 

该函数是一个有效的React组件,它接收一个单一的“props”对象并返回了一个React元素。我们之所以称这种类型的组件为函数定义组件,是因为从字面上来看,它就是一个JavaScript函数。

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

class Welcome extends React.Component {
  render() {
    return 

Hello, { this.props.name}

; } }

 

上面两个组件在React中是相同的。

组件渲染

在前面,我们遇到的React元素都只是DOM标签:

const element = 
;

 

然而,React元素也可以是用户自定义的组件:

const element = ;

 

当React遇到的元素是用户自定义的组件,它会将JSX属性作为单个对象传递给该组件,这个对象称之为“props”。

例如,这段代码会在页面上渲染出"Hello,Sara":

function Welcome(props) {
  return 

Hello, {props.name}

; } // ... class App extends Component { render() { return (
); } } // ...

 

我们来回顾一下在这个例子中发生了什么:

  1. 我们对元素调用了ReactDOM.render()方法。
  2. React将{name: 'Sara'}作为props传入并调用Welcome组件。
  3. Welcome组件将

    Hello, Sara

    元素作为结果返回。
  4. 将DOM更新为

    Hello, Sara

警告:

组件名称必须以大写字母开头。

例如,

 表示一个DOM标签,但  表示一个组件,并且在使用该组件时你必须定义或引入它。

组合组件

组件可以在它的输出中引用其它组件,这就可以让我们用同一组件来抽象出任意层次的细节。在React应用中,按钮、表单、对话框、整个屏幕的内容等,这些通常都被表示为组件。

例如,我们可以创建一个App组件,用来多次渲染Welcome组件:

function Welcome(props) {
  return 

Hello, {props.name}

; } function App() { return (
); }

 

通常,一个新的React应用程序的顶部是一个App组件。但是,如果要将React集成到现有应用程序中,则可以从下而上使用像Button这样的小组件作为开始,并逐渐运用到视图层的顶部。

警告:

组件的返回值只能有一个根元素。这也是我们要用一个

来包裹所有元素的原因。

提取组件

你可以将组件切分为更小的组件,这没什么好担心的。

例如,来看看这个Comment组件:

function Comment(props) {
  return (
    
src={props.author.avatarUrl} alt={props.author.name} />
{props.author.name}
{props.text}
{formatDate(props.date)}
); }

 

这个组件接收author(对象)、text(字符串)、以及date(Date对象)作为props,用来描述一个社交媒体网站上的评论。

这个组件由于嵌套,变得难以被修改,可复用的部分也难以被复用。所以让我们从这个组件中提取出一些小组件。

首先,我们来提取Avatar组件:

function Avatar(props) {
  return (
    
      src={props.user.avatarUrl}
      alt={props.user.name}
    />
  );
}

 

Avatar作为Comment的内部组件,不需要知道是否被渲染。因此我们将author改为一个更通用的名字user

我们建议从组件自身的角度来命名props,而不是根据使用组件的上下文命名。

现在我们可以对Comment组件做一些小小的调整:

function Comment(props) {
  return (
    
{props.author.name}
{props.text}
{formatDate(props.date)}
); }

 

提取组件一开始看起来像是一项单调乏味的工作,但是在大型应用中,构建可复用的组件完全是值得的。当你的UI中有一部分重复使用了好几次(比如,ButtonPanelAvatar),或者其自身就足够复杂(比如,AppFeedStoryComment),类似这些都是抽象成一个可复用组件的绝佳选择,这也是一个比较好的做法。

Props的只读性

无论是使用函数或是类来声明一个组件,它决不能修改它自己的props。来看这个sum函数:

function sum(a, b) {
  return a + b;
}

 

类似于上面的这种函数称为“纯函数”,它没有改变它自己的输入值,当传入的值相同时,总是会返回相同的结果。

与之相对的是非纯函数,它会改变它自身的输入值:

function withdraw(account, amount) {
  account.total -= amount;
}

 

React是非常灵活的,但它也有一个严格的规则:

所有的React组件必须像纯函数那样使用它们的props。

当然,应用的界面是随时间动态变化的,后面会介绍一种称为“state”的新概念,State可以在不违反上述规则的情况下,根据用户操作、网络响应、或者其他状态变化,使组件动态的响应并改变组件的输出。

组件的生命周期

React组件的生命周期图解

React 生命周期图解

组件会随着组件的props和state改变而发生变化,它的DOM也会有相应的变化。一个组件就是一个状态机:对于特定的输入,它总会返回一致的输出。

React组件提供了生命周期的钩子函数去响应组件不同时刻的状态,组件的生命周期如下:

  • 实例化阶段
  • 存在期阶段
  • 销毁期阶段

实例化阶段

首次调用组件时,实例化阶段有以下方法会被调用(注意顺序,从上到下先后执行):

getDefaultProps

这个方法是用来设置组件默认的props,组件生命周期只会调用一次。但是只适合React.createClass直接创建的组件,使用ES6/ES7创建的这个方法不可使用,ES6/ES7可以使用下面方式:

//es7
class Component {
  static defaultProps = {}
}

 

getInitialState

设置state初始值,在这个方法中你已经可以访问到this.props。getDefaultProps只适合React.createClass使用。使用ES6初始化state方法如下:

class Component extends React.Component {
  constructor(props){
    super(props);
    this.state = {
      render: true,
    }
  }
}
// 或者这样, es6 stage3

class Component extends React.Component{
  state = {
    render: true
  }
  render(){
      return false;}
}

 

componentWillMount

此方法会在组件首次渲染之前调用,这个是在render方法调用前可修改state的最后一次机会。这个方法很少用到。

render

这个方法以后大家都应该会很熟悉,JSX通过这里,解析成对应的虚拟DOM,渲染成最终效果。格式大致如下:

class Component extends React.Component{
  render(){
    return (
       
) } }

 

componentDidMount

这个方法在首次真实的DOM渲染后调用(仅此一次)当我们需要访问真实的DOM时,这个方法就经常用到。如何访问真实的DOM这里就不想说了。当我们需要请求外部接口数据,一般都在这里处理。

存在期

实例化后,当props或者state发生变化时,下面方法依次被调用:

componentWillReceiveProps

没当我们通过父组件更新子组件props时(这个也是唯一途径),这个方法就会被调用。

componentWillReceiveProps(nextProps){}

shouldComponentUpdate

字面意思,是否应该更新组件,默认返回true。当返回false时,后期函数就不会调用,组件不会在次渲染。

shouldComponentUpdate(nextProps,nextState){}

componentWillUpdate

字面意思组件将会更新,props和state改变后必调用。

render

跟实例化时的render一样,不多说

componentDidUpdate

这个方法在更新真实的DOM成功后调用,当我们需要访问真实的DOM时,这个方法就也经常用到。

销毁期

销毁阶段,只有一个函数被调用:

componentWillUnmount

没当组件使用完成,这个组件就必须从DOM中销毁,此时该方法就会被调用。当我们在组件中使用了setInterval,那我们就需要在这个方法中调用clearTimeout。

参考:React组件生命周期

React组件的状态state

我们可以通过this.props获取一个组件的属性,另外还可以通过this.state获取组件的私有状态(也就是私有数据)。

构造函数初始化内部的状态

import React, { Component } from 'react'

class Clock extends Component {
  constructor(props) {
    super(props);
    // 构造函数中设置状态
    this.state = {
      DateStr: new Date().toLocaleDateString()
    };
  }

  render () {
    return (
      

当前时间是: { this.state.DateStr}

) } } export default Clock

 

关于 setState() 这里有三件事情需要知道

不要直接更新状态

例如,此代码不会重新渲染组件:

// Wrong
this.state.comment = 'Hello';

应当使用 setState():

// Correct
this.setState({comment: 'Hello'});

 

构造函数是唯一能够初始化 this.state 的地方。

状态更新可能是异步的

React 可以将多个setState() 调用合并成一个调用来提高性能。

因为 this.props 和 this.state 可能是异步更新的,你不应该依靠它们的值来计算下一个状态。

例如,此代码可能无法更新计数器:

// Wrong
this.setState({ counter: this.state.counter + this.props.increment, });

要修复它,请使用第二种形式的 setState() 来接受一个函数而不是一个对象。 该函数将接收先前的状态作为第一个参数,将此次更新被应用时的props做为第二个参数:

// Correct
this.setState((prevState, props) => ({
  counter: prevState.counter + props.increment
}));

 

上方代码使用了箭头函数,但它也适用于常规函数:

// Correct
this.setState(function(prevState, props) {
  return {
    counter: prevState.counter + props.increment
  };
});

 

状态单独更新和自动合并

当你调用 setState() 时,React 将你提供的对象合并到当前状态。

例如,你的状态可能包含一些独立的变量:

  constructor(props) {
    super(props); this.state = { posts: [], comments: [] }; }

你可以调用 setState() 独立地更新它们:

 componentDidMount() {
    fetchPosts().then(response => {
      this.setState({
        posts: response.posts
      });
    });

    fetchComments().then(response => {
      this.setState({
        comments: response.comments
      });
    });
  }

 

这里的合并是浅合并,也就是说this.setState({comments})完整保留了this.state.posts,但完全替换了this.state.comments

数据自顶向下流动

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

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

组件可以选择将其状态作为属性传递给其子组件:

It is { this.state.date.toLocaleTimeString()}.

 

这也适用于用户定义的组件:

this.state.date} />

 

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

function FormattedDate(props) {
  return 

It is {props.date.toLocaleTimeString()}.

; }

 

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

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

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

function App() {
  return (
    
); }

 

每个 Clock 建立自己的定时器并且独立更新。 在 React 应用程序中,组件是有状态还是无状态被认为是可能随时间而变化的组件的实现细节。 可以在有状态组件中使用无状态组件,反之亦然。

综合案例自动计时的时钟

import React, { Component } from 'react'

class Clock extends Component {
  constructor(props) {
    super(props);
    this.state = {
      DateStr: new Date().toLocaleTimeString(),
      timer: null
    };
  }
  componentDidMount() {
    this.setState({
      timer: setInterval(() => {
        this.setState({
          DateStr: new Date().toLocaleTimeString()
        });
      }, 1000)
    });
  }

  componentWillUnmount() {
    clearInterval(this.state.timer);
  }

  render () {
    return (
      

当前时间是: { this.state.DateStr}

) } } export default Clock

 

React的事件处理

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

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

JSX语法中使用{}执行js代码。

例如,传统的 HTML:

React 中稍稍有点不同:

 

在 React 中另一个不同是你不能使用返回 false 的方式阻止默认行为。你必须明确的使用 preventDefault。例如,传统的 HTML 中阻止链接默认打开一个新页面,你可以这样写:

href="#" onclick="console.log('The link was clicked.'); return false"> Click me >

在 React,应该这样来写:

 

在这里,e 是一个合成事件。React 根据 W3C spec 来定义这些合成事件,所以你不需要担心跨浏览器的兼容性问题。

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

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

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

    // This binding is necessary to make `this` work in the callback
    this.handleClick = this.handleClick.bind(this);
  }

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

  render() {
    return (
      
    );
  }
}

ReactDOM.render(
  ,
  document.getElementById('root')
);

 

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

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

如果使用 bind 让你很烦,这里有两种方式可以解决。如果你正在使用实验性的属性初始化器语法,你可以使用属性初始化器来正确的绑定回调函数:

class LoggingButton extends React.Component {
  // This syntax ensures `this` is bound within handleClick.
  // Warning: 最新的es的语法,暂时是在stage3
  handleClick = () => {
    console.log('this is:', this);
  }

  render() {
    return (
      
    );
  }
}

 

这个语法在 Create React App 中默认开启。

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

class LoggingButton extends React.Component {
  handleClick() {
    console.log('this is:', this);
  }

  render() {
    // This syntax ensures `this` is bound within handleClick
    return (
      
    );
  }
}

 

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

demo

import React, { Component } from 'react';
class App extends Component {

  constructor(option) {
    super(option);
    this.handler = this.handler.bind(this);
  }

  handler(e) {
    console.log(e);
    console.log(this);
  }

  hanlderClick = (e) => {
     console.log(e);
     e.target.innerText = Date.now();
  };
  handlerButtonClick(e) {
    console.log('button click');
  }

  render() {
    return (
      

aicoder.com

{console.log(this); console.log(3);}}>nihao

); } } export default App;

 

向事件处理程序传递参数

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


 

上述两种方式是等价的,分别通过 arrow functions 和 Function.prototype.bind 来为事件处理函数传递参数。

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

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

class Popper extends React.Component{
    constructor(){
        super();
        this.state = {name:'Hello world!'};
    }
    preventPop(name, e){    //事件对象e要放在最后
        e.preventDefault();
        alert(name);
    }
    render(){
        return (
            

hello

{ /* Pass params via bind() method. */} this.preventPop.bind(this,this.state.name)}>Click
); } }

 

条件渲染

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

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

先来看两个组件:

function UserGreeting(props) {
  return 

Welcome back!

; } function GuestGreeting(props) { return

Please sign up.

; }

 

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

function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  if (isLoggedIn) {
    return ;
  }
  return ;
}

ReactDOM.render(
  // Try changing to isLoggedIn={true}:
  false} />,
  document.getElementById('root')
);

 

 

此示例根据 isLoggedIn 的值渲染不同的问候语。

元素变量

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

再来看两个新组件分别代表注销和登录:

function LoginButton(props) {
  return (
    
  );
}

function LogoutButton(props) {
  return (
    
  );
}

 

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

它会根据当前的状态来渲染  或 ,它也将渲染前面例子中的 

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 = this.handleLogoutClick} />;
    } else {
      button = this.handleLoginClick} />;
    }

    return (
      
{button}
); } } ReactDOM.render( , document.getElementById('root') );

 

 

声明变量并使用 if 语句是条件渲染组件的不错的方式,但有时你也想使用更简洁的语法,在 JSX 中有如下几种方法。

与运算符 &&

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

function Mailbox(props) {
  const unreadMessages = props.unreadMessages;
  return (
    

Hello!

{unreadMessages.length > 0 &&

You have {unreadMessages.length} unread messages.

}
); } const messages = ['React', 'Re: React', 'Re:Re: React']; ReactDOM.render( , document.getElementById('root') );

 

 

之所以能这样做,是因为在 JavaScript 中,true && expression 总是返回 expression,而 false && expression 总是返回 false

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

三目运算符

条件渲染的另一种方法是使用 JavaScript 的条件运算符 condition ? true : false

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

render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    
The user is {isLoggedIn ? 'currently' : 'not'} logged in.
); }

 

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

render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    
{isLoggedIn ? ( this.handleLogoutClick} /> ) : ( this.handleLoginClick} /> )}
); }

 

像在 JavaScript 中一样,你可以根据团队的习惯选择更易读的方式。还要记住如果条件变得过于复杂,可能就是提取组件的好时机了。

阻止组件渲染

在极少数情况下,你可能希望隐藏组件,即使它被其他组件渲染。让 render 方法返回 null 而不是它的渲染结果即可实现。

在下面的例子中, 根据属性 warn 的值条件渲染。如果 warn 的值是 false,则组件不会渲染:

function WarningBanner(props) {
  if (!props.warn) {
    return null;
  }

  return (
    
Warning!
); } 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 (
this.state.showWarning} />
); } } ReactDOM.render( , document.getElementById('root') );

 

 

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

React列表 & Keys

首先,让我们看下在Javascript中如何转化列表

如下代码,我们使用map()函数让数组中的每一项翻倍,我们得到了一个新的数列doubled

const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((number) => number * 2);
console.log(doubled);

 

代码打印出[2, 4, 6, 8, 10]

在React中,把数组转化为数列元素的过程是相似的

渲染多个组件

你可以通过使用{}在JSX内构建一个元素集合

下面,我们使用Javascript中的map()方法遍历numbers数组。对数组中的每个元素返回

  • 标签,最后我们得到一个数组listItems

    const numbers = [1, 2, 3, 4, 5];
    const listItems = numbers.map((number) =>
      
  • {number}
  • );

     

    我们把整个listItems插入到ul元素中,然后渲染进DOM:

    ReactDOM.render(
      
      {listItems}
    , document.getElementById('root') );

     

     

    这段代码生成了一个1到5的数字列表

    基础列表组件

    通常你需要渲染一个列表到组件中

    我们可以把前面的例子重构成一个组件。这个组件接收numbers数组作为参数,输出一个无序列表。

    function NumberList(props) {
      const numbers = props.numbers;
      const listItems = numbers.map((number) =>
        
  • {number}
  • ); return (
      {listItems}
    ); } const numbers = [1, 2, 3, 4, 5]; ReactDOM.render( , document.getElementById('root') );

     

    当我们运行这段代码,将会看到一个警告 a key should be provided for list items ,意思是当你创建一个元素时,必须包括一个特殊的 key 属性。我们将在下一节讨论这是为什么。

    让我们来给每个列表元素分配一个 key 来解决上面的那个警告:

    function NumberList(props) {
      const numbers = props.numbers;
      const listItems = numbers.map((number) =>
        
  • {number}
  • ); return (
      {listItems}
    ); } const numbers = [1, 2, 3, 4, 5]; ReactDOM.render( , document.getElementById('root') );

     

     

    Keys

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

    const numbers = [1, 2, 3, 4, 5];
    const listItems = numbers.map((number) =>
      
  • {number}
  • );

     

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

    const todoItems = todos.map((todo) =>
      
  • {todo.text}
  • );

     

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

    const todoItems = todos.map((todo, index) =>
      // Only do this if items have no stable IDs
      
  • {todo.text}
  • );

     

    如果列表项目的顺序可能会变化,我们不建议使用索引来用作键值,因为这样做会导致性能的负面影响,还可能引起组件状态问题。如果你想要了解更多,请点击深度解析key的必要性。如果你选择不指定显式的键值,那么React将默认使用索引用作为列表项目的键值。

    这里有一篇文章 in-depth explanation about why keys are necessary ,要是你有兴趣了解更多的话。

    用keys提取组件

    元素的key只有放在其环绕数组的上下文中才有意义。

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

  • 元素上。

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

     

     

    一个好的大拇指法则:元素位于map()方法内时需要设置键属性。

    键(key)只是在兄弟之间必须唯一

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

    function Blog(props) {
      const sidebar = (
        
      {props.posts.map((post) =>
    • {post.title}
    • )}
    ); const content = props.posts.map((post) =>

    {post.title}

    {post.content}

    ); return (
    {sidebar}
    {content}
    ); } 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( , document.getElementById('root') );

     

     

    key会作为给React的提示,但不会传递给你的组件。如果您的组件中需要使用和key相同的值,请用其他属性名显式传递这个值:

    const content = posts.map((post) =>
      <Post
        key={post.id}
        id={post.id}
        title={post.title} />
    );

     

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

    在jsx中嵌入map()

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

    function NumberList(props) {
      const numbers = props.numbers;
      const listItems = numbers.map((number) =>
        {number.toString()}
                  value={number} />
      );
      return (
        
      {listItems}
    ); }

     

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

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

     

     

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

    表单

    HTML表单元素与React中的其他DOM元素有所不同,因为表单元素生来就保留一些内部状态。例如,下面这个表单只接受一个唯一的name。

     

    当用户提交表单时,HTML的默认行为会使这个表单跳转到一个新页面。在React中亦是如此。但大多数情况下,我们都会构造一个处理提交表单并可访问用户输入表单数据的函数。实现这一点的标准方法是使用一种称为“受控组件”的技术。

    受控组件

    在HTML当中,像,

  •  

    在React中,