freeCodeCamp--React学习笔记

目录

1 渲染 HTML 元素为 DOM 树

2 了解 JSX 的自动闭合

3 创建组件 

3.1 JS函数创建组件----无状态组件

3.2 class创建组件

3.3 用组合的方式创建一个 React 组件

 3.4 创建一个有状态的组件

3.4.1 用 this.setState 设置状态

3.4.2 将 this 绑定到 Class 方法上

3.4.3 使用 State 切换元素

4 将 class 组件渲染到 DOM 树

5 传参 

5.1 将 Props 传递给无状态函数组件

5.2 传递一个数组作为 Props

5.3 使用默认的 Props

5.4 覆盖默认的 Props

5.5 使用 PropTypes 来定义 Props 的类型

5.6 使用 this.props 访问 Props

5.8 传递回调作为 Props

6 小例子 

6.1 一个简单的计数器

6.2 创建一个可以控制的输入框

6.3 创建一个可以控制的表单

7 其他 

7.1 使用生命周期方法:componentWillMount

7.2 使用生命周期方法:componentDidMount

7.3 添加事件侦听器

7.4 使用 shouldComponentUpdate 优化重新渲染

7.5 内联样式

7.6 使用 && 获得更简洁的条件

7.7 使用三元表达式进行条件渲染

7.8 根据 Props 有条件地渲染

7.9 使用 Array.map() 动态渲染元素

7.10 给同级元素一个唯一的键属性

7.11 使用 Array.Filter() 动态过滤数组

7.12 用 renderToString 在服务器上渲染 React


渲染 HTML 元素为 DOM 树

ReactDOM 提供了一个简单的方法来将 React 元素呈现给 DOM,如下所示:

ReactDOM.render(componentToRender, targetNode)

其中第一个参数是要渲染的 React 元素组件,第二个参数是组件将要渲染到的 DOM 节点

并且必须在 JSX 元素声明之后调用 ReactDOM.render(),就像在使用变量之前必须声明它一样。

示例:

代码编辑器有一个简单的 JSX 组件。使用 ReactDOM.render() 方法将该组件渲染到页面。 可以将定义好的 JSX 元素直接作为第一个参数传入,然后使用 document.getElementById() 来选择要渲染到的 DOM 节点, 在这个示例中,请渲染到 id='challenge-node' 的 div 中。 确保没有修改 JSX 常量。

const JSX = (
  

Hello World

Lets render this to the DOM

); // 修改这行下面的代码 ReactDOM.render( JSX,document.getElementById('challenge-node') );

2 了解 JSX 的自动闭合

关于属性大小写问题:

JSX中使用驼峰法,因为小写是原生HTML的关键字,比如添加class属性,HTML中使用class,JSX中使用className。

JSX 不同于 HTML 的另一个重要方面是自闭合标签。

在HTML中,几乎所有的标签都有一个开始和结束标签:

,结束标签在你要关闭的标签名之前始终具有正斜杠。 但是,HTML 中有一些称为 “自闭合标签” 的特殊实例,它们在另一个标签开始之前,不需要开始和结束标签都存在。

例如,换行标签可以写成 
 或者 
,但是不应该写成 

,因为它不包含任何内容。

在 JSX 中,规则略有不同。 任何 JSX 元素都可以使用自闭合标签编写,并且每个元素都必须关闭。

例如,为了通过编译换行标签必须始终编写为 

另一方面 

 可以写成 
 或者 
。 不同之处在于,在第一个语法版本中,无法在 
 中包含任何内容。 在后面的挑战中你会发现,这种语法在渲染 React 组件时非常有用。(往下看两节再回来看这一段就明白很多。)

3 创建组件 

3.1 JS函数创建组件----无状态组件

组件是 React 的核心,React 中的所有内容都是一个组件。

有两种方法可以创建 React 组件。

第一种方法是使用 JavaScript 函数。 以这种方式定义组件会创建无状态功能组件。 

要用函数创建组件,只需编写一个返回 JSX 或 null 的 JavaScript 函数。 需要注意的一点是,React 要求你的函数名以大写字母开头。 下面是一个无状态功能组件的示例,该组件在 JSX 中分配一个 HTML 的 class:

const DemoComponent = function() {
  return (
    
); };

翻译完成后, 

 将有一个 customClass 的 CSS class。

因为 JSX 组件代表 HTML,所以你可以将几个组件放在一起以创建更复杂的 HTML 页面。 这是 React 提供的组件架构的关键优势之一。 它允许用许多独立的组件组合成 UI。 这使得构建和维护复杂的用户界面变得更加容易。

3.2 class创建组件

定义 React 组件的另一种方法是使用 ES6 的 class语法。

在以下示例中,Kitten 扩展了React.Component

class Kitten extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      

Hi

); } }

这将创建一个 ES6 类 Kitten,它扩展了 React.Component 类。

因此,Kitten 类现在可以访问许多有用的 React 功能,例如本地状态和生命周期钩子。

另请注意,Kitten 类中定义了一个调用 super() 方法的 constructor。 它使用 super() 调用父类的构造函数,即本例中的 React.Component

构造函数是使用 class 关键字创建的特殊方法,它在实例初始化之前调用。

最佳做法是在组件的 constructor 里调用 super,并将 props 传递给它们, 这样可以保证组件能够正确地初始化。 目前为止 ,需要知道这些代码是必要的。 很快会了解到到构造函数的其他用途以及 props。 

3.3 用组合的方式创建一个 React 组件

现在来看看如何组合多个 React 组件。

想象一下,现在正在构建一个应用程序,并创建了三个组件:NavbarDashboard 和 Footer

要将这些组件组合在一起,可以创建一个 App 父组件,将这三个组件分别渲染成为子组件

要在 React 组件中渲染一个子组件,需要在 JSX 中包含作为自定义 HTML 标签编写的组件名称。 例如,在 render 方法中,可以这样编写:

return (
 
  
  
  
)

当 React 遇到一个自定义 HTML 标签引用另一个组件的时(如本例所示,组件名称包含在 < /> 中),它在自定义标签的位置渲染该组件的标签。

这可以说明 App 组件和 NavbarDashboard 以及 Footer 之间的父子关系。

 3.4 创建一个有状态的组件

React 中最重要的主题之一是 state。 state 包含应用程序需要了解的任何数据,这些数据可能会随时间而变化。 应用程序能够响应 state 的变更,并在必要时显示更新后的 UI。 React 为现代 Web 应用程序的状态管理提供了一个很好的解决方案。

可以在类组件的 constructor 上声明 state 属性来在 React 组件中创建 state, 它在创建时使用 state 初始化组件。 state 属性必须设置为 JavaScript object(对象)。 声明如下:

this.state = {

}

可以在组件的整个生命周期内访问 state 对象, 可以更新它、在 UI 中渲染它,也可以将其作为 props 传递给子组件。 state 对象的使用可以很简单,亦可以很复杂,就看你怎么用了。 请注意,必须通过扩展 React.Component 来创建类组件,以便像这样创建 state

定义了组件的初始 state 之后,就可以在要渲染的 UI 中显示它。 如果组件是有状态的,它将始终可以访问 render() 方法中 state 的数据。 就可以使用 this.state 访问数据。

如果想在 render 方法的 return 中访问 state 值,必须把这个值用花括号括起来。

state 是 React 组件中最强大的特性之一, 它可以跟踪应用程序中的重要数据,并根据数据的变化渲染 UI。 如果数据发生变化,UI 也会随之改变。 React 使用所谓的虚拟 DOM 来跟踪幕后的变化。 当 state 数据更新时,它会使用该数据触发组件的重新渲染 -- 包括接收 prop 数据的子组件。 React 只在必要的时候更新实际的 DOM, 这意味着你不必担心 DOM 的变更, 只需声明 UI 的外观即可。

注意,如果组件是有状态的,其它组件并不知道它的 state。 它的 state 是完全封装的,或者是局限于组件本身的,除非你将 state 数据作为 props 传递给子组件。 封装 state 的概念非常重要,因为它允许编写特定的逻辑,然后将该逻辑包含并隔离在代码中的某个位置。还有另一种方法可以访问组件中的 state。 在 render() 方法中,在 return 语句之前,可以直接编写 JavaScript。 例如,可以声明函数、从 state 或 props 中访问数据、对此数据执行计算等。 然后,可以将任何数据赋值给 return 语句中可以访问的变量。

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: 'freeCodeCamp'
    }
  }
  render() {
    // 修改这行下面的代码
    const name=this.state.name;
    // 修改这行上面的代码
    return (
      
{ /* 修改这行下面的代码 */ }

{name}

{ /* 修改这行上面的代码 */ }
); } };

3.4.1 用 this.setState 设置状态

前面的挑战涵盖了组件的 state 以及如何在 constructor 中初始化 state。 还有一种方法可以更改组件的 state, React 提供了 setState 方法来更新组件的 state。 在组件类中调用 setState 方法如下所示:this.setState(),传入键值对的对象, 其中键是 state 属性,值是更新后的 state 数据。 例如,如果我们在 state 中存储 username,并想要更新它,代码如下所示:

this.setState({
  username: 'Lewis'
});

React 要求永远不要直接修改 state,而是在 state 发生改变时始终使用 this.setState()。 此外,应该注意,React 可以批量处理多个 state 更新以提高性能。 这意味着通过 setState 方法进行的 state 更新可以是异步的。

3.4.2 将 this 绑定到 Class 方法上

除了设置和更新 state 之外,还可以为组件类定义方法。 类方法通常需要使用 this 关键字,以便它可以访问方法中类的属性(例如 state 和 props)。 有几种方法可以让类方法访问 this

一种常见的方法是在构造函数中显式地绑定 this,这样当组件初始化时,this 就会绑定到类方法。 你可能已经注意到上一个挑战在构造函数中的 handleClick 方法使用了 

this.handleClick = this.handleClick.bind(this)

然后,当在类方法中调用像 this.setState() 这样的函数时,this 指的是这个类,而不是 undefined

3.4.3 使用 State 切换元素

有时可能在更新状态的时候想知道上一个状态是什么。 但是状态更新是异步的,这意味着 React 可能会把多个 setState() 集中在一起批量更新。 所以计算下一个值时 this.state 或者 this.props 不能作为当前值。 所以最好不要写如下的代码:

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

正确的做法是,给 setState 传入一个函数,这个函数可以访问 state 和 props。 给 setState 传入函数可以保证 state 和 props 是正确的值。 代码可以重写为这样:

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

如果只需要 state,那么用下面没有 props 的格式也是可以的:

this.setState(state => ({
  counter: state.counter + 1
}));

注意一定要把 object 放在括号里,否则 JavaScript 会认为这只是代码片段。

练习:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      visibility: false
    };
    // 修改这行下面的代码
    this.toggleVisibility=this.toggleVisibility.bind(this);
    // 修改这行上面的代码
  }
  // 修改这行下面的代码
  toggleVisibility() {
    this.setState(state=> ({
    visibility:!(state.visibility)
  }));
  }
  
  // 修改这行上面的代码
  render() {
    if (this.state.visibility) {
      return (
        

Now you see me!

); } else { return (
); } } }

4 将 class 组件渲染到 DOM 树

还记不记得在之前的示例中使用 ReactDOM API 将 JSX 元素渲染到 DOM, 这与渲染 React 组件的过程十分相似。如果不调用 ReactDOM API,编写的任何 React 代码都不会渲染到 DOM。

复习一下语法: ReactDOM.render(componentToRender, targetNode)。 第一个参数是要渲染的 React 组件。 第二个参数是要在其中渲染该组件的 DOM 节点。

传递到ReactDOM.render() 的React 组件与 JSX 元素略有不同。 对于 JSX 元素,传入的是要渲染的元素的名称。 但是,对于 React 组件,需要使用与渲染嵌套组件相同的语法,例如

ReactDOM.render(, targetNode)

此语法用于 ES6 class 组件和函数组件都可以。

示例:

在后台引入了 Fruits 和 Vegetables 组件。

将两个组件渲染为 TypesOfFood 组件的子组件,然后将 TypesOfFood 渲染到 DOM 节点, 在这个挑战中,请渲染到 id='challenge-node'的 div 中。

class TypesOfFood extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
      

Types of Food:

{/* 修改这行下面的代码 */} {/* 修改这行上面的代码 */}
); } }; // 修改这行下面的代码----注意这个渲染的component是一个组件,与前例中渲染元素不同 ReactDOM.render( ,document.getElementById('challenge-node') );

5 传参 

5.1 将 Props 传递给无状态函数组件

现在是时候看看 React 中的另一个常见特性 props 了。

在 React 中,可以将属性传递给子组件。

假设有一个 App 组件,该组件渲染了一个名为 Welcome 的子组件,它是一个无状态函数组件。

可以通过以下方式给 Welcome 传递一个 user 属性:


  

可以把创建的 React 支持的自定义 HTML 属性传递给组件, 在上面的例子里,将创建的属性 user 传递给组件 Welcome

由于 Welcome 是一个无状态函数组件,它可以像这样访问该值:

const Welcome = (props) => 

Hello, {props.user}!

调用 props 这个值是常见做法,当处理无状态函数组件时,基本上可以将其视为返回 JSX 的函数的参数。 这样,你就可以在函数体中访问该值。 但对于类组件,访问方式会略有不同。

示例:

代码编辑器中有 Calendar 和 CurrentDate 组件。

从 Calendar 组件渲染 CurrentDate 时,从 JavaScript 的 Date 对象分配当前日期,并将其作为 date 属性传入。

然后访问 CurrentDate 组件的 prop,并在 p 标签中显示其值。

请注意,要将 prop 的值视为 JavaScript,必须将它们括在花括号中,例如date={Date()}

const CurrentDate = (props) => {
  return (
    
{ /* 修改这行下面的代码 */ }

The current date is: {props.date}

{ /* 修改这行上面的代码 */ }
); }; class Calendar extends React.Component { constructor(props) { super(props); } render() { return (

What date is it?

{ /* 修改这行下面的代码 */ } { /* 修改这行上面的代码 */ }
); } };

5.2 传递一个数组作为 Props

上一个示例演示了如何将来自父组件的信息作为 props 传递给子组件。

接下来着眼于如何将数组作为 props 传递。

要将数组传递给 JSX 元素,必须将其视为 JavaScript 并用花括号括起来。


  

这样,子组件就可以访问数组属性 colors。访问属性时可以使用 join() 等数组方法。

 const ChildComponent = (props) => 

{props.colors.join(', ')}

 

这将把所有 colors 数组项连接成一个逗号分隔的字符串并生成:

 

green, blue, red

 

稍后,将了解在 React 中渲染数组数据的其他常用方法。

练习:

  1. 代码编辑器中有 List 和 ToDo 组件。
  2. 在 ToDo 组件中渲染每个 List 时,传入 tasks 属性并将其分配给待办任务数组,例如 ["walk dog", "workout"]
  3. 然后访问 List 组件中的 tasks 数组,在p元素中显示其值。
  4. 使用 join(", ") 把 props.tasks 数组作为逗号分隔列表显示在 p 元素中。
  5. 今天的列表应该至少有 2 个任务,明天的列表应该至少有 3 个任务。
const List = (props) => {
  { /* 修改这行下面的代码 */ }
  return 

{props.tasks.join(',')}

{ /* 修改这行上面的代码 */ } }; class ToDo extends React.Component { constructor(props) { super(props); } render() { return (

To Do Lists

Today

{ /* 修改这行下面的代码 */ }

Tomorrow

{ /* 修改这行上面的代码 */ }
); } };

5.3 使用默认的 Props

React 还有一个设置默认 props 的选项。

可以将默认 props 作为组件本身的属性分配给组件,React 会在必要时分配默认 prop。

如果没有显式的提供任何值,这允许指定 prop 值应该是什么。

例如,如果声明 

MyComponent.defaultProps = { location: 'San Francisco' }

即定义一个 location 属性,并且其值在没有另行定义的情况下被设置为字符串 San Francisco。 如果 props 未定义,则 React 会分配默认 props,但如果你将 null 作为 prop 的值,它将保持 null

5.4 覆盖默认的 Props

在 React 中,设置默认的 props 是一个很有用的特性, 直接设置组件的 prop 值即可覆盖默认 props。

5.5 使用 PropTypes 来定义 Props 的类型

React 提供了有用的类型检查特性,以验证组件是否接收了正确类型的 props。 例如,应用程序调用 API 来检索数据是否是数组,然后将数据作为 prop 传递给组件。 可以在组件上设置 propTypes,以要求数据的类型为 array。 当数据是任何其它类型时,都会抛出警告。

当提前知道 prop 的类型时,最佳实践是设置其 propTypes。 可以为组件定义 propTypes 属性,方法与定义 defaultProps 相同。 这样做将检查给定键的 prop 是否是给定类型。 这里有一个示例,表示名为 handleClick 的 prop 应为 function 类型:

MyComponent.propTypes = { handleClick: PropTypes.func.isRequired }

在上面的示例中,PropTypes.func 部分检查 handleClick 是否为函数。 添加isRequired,告诉 React handleClick 是该组件的必需属性。 如果没有那个属性,将出现警告。 还要注意 func 代表 function 。

在 7 种 JavaScript 原语类型中, function 和 boolean (写为 bool )是唯一使用异常拼写的两种类型。 除了原始类型,还有其他类型可用

例如,你可以检查 prop 是否为 React 元素。 请参阅文档以获取所有选项。

注意:在 React v15.5.0 中, PropTypes 可以从 React 中单独引入,例如:import PropTypes from 'prop-types';

练习:

为 Items 组件定义 propTypes,以要求 quantity 作为 prop,并验证它是否为 number 类型。

const Items = (props) => {
  return 

Current Quantity of Items in Cart: {props.quantity}

}; // 修改这行下面的代码 Items.propTypes={quantity:PropTypes.number.isRequired } // 修改这行上面的代码 Items.defaultProps = { quantity: 0 }; class ShoppingCart extends React.Component { constructor(props) { super(props); } render() { return } };

5.6 使用 this.props 访问 Props

前几项挑战涵盖了将 props 传递给子组件的基本方法。 但是,倘若接收 prop 的子组件不是无状态函数组件,而是一个 ES6 类组件又当如何呢? ES6 类组件访问 props 的方法略有不同。

任何时候,如果要引用类组件本身,可以使用 this 关键字。 要访问类组件中的 props,需要在在访问它的代码前面添加 this。 例如,如果 ES6 类组件有一个名为 data 的 prop,可以在 JSX 中这样写:{this.props.data}

练习:

在父组件 ResetPassword 中渲染 ReturnTempPassword 组件的一个实例。 在这里,为 ReturnTempPassword 提供一个 tempPassword prop,并赋值一个长度至少为 8 个字符的字符串。 在子组件 ReturnTempPassword 中,访问 strong 标签中的 tempPassword prop,以确保用户看到临时密码。

class ReturnTempPassword extends React.Component {
  constructor(props) {
    super(props);

  }
  render() {
    return (
        
{ /* 修改这行下面的代码 */ }

Your temporary password is: {this.props.tempPassword}

{ /* 修改这行上面的代码 */ }
); } }; class ResetPassword extends React.Component { constructor(props) { super(props); } render() { return (

Reset Password

We've generated a new temporary password for you.

Please reset this password from your account settings ASAP.

{ /* 修改这行下面的代码 密码是随意写的*/ } { /* 修改这行上面的代码 */ }
); } };

5.7 将 State 作为 Props 传递给子组件

在之前的挑战中,看到了很多将 props 传递给子 JSX 元素和子 React 组件的例子。 你可能想知道那些 props 是从哪里来的。 一个常见的模式是:有状态组件中包含对应用程序很重要的 state,然后用它渲染子组件。 如果想让这些组件能够访问该 state 的某些部分,就把这些部分作为 props 传入。

例如,有一个 App 组件可以渲染 Navbar 以及其他组件。 App 里的 state 包含大量用户信息,但 Navbar 只需要访问用户的用户名,以便显示它。 将该 state 作为 prop 传递给Navbar组件。

这个模式说明了 React 中的一些重要范例。 第一个是单向数据流, state 沿着应用程序组件树的一个方向流动,从有状态父组件到子组件, 子组件只接收它们需要的 state 数据。 第二,复杂的有状态应用程序可以分解成几个,或者可能是一个单一的有状态组件。 其余组件只是从父组件简单的接收 state 作为 props,并从该 state 渲染 UI。 它开始创建一种分离,在这种分离中,state 管理在代码的一部分中处理,而 UI 渲染在另一部分中处理。 将 state 逻辑与 UI 逻辑分离是 React 的关键原则之一。 当它被正确使用时,它使得复杂的、有状态的应用程序的设计变得更容易管理。

MyApp 组件是有状态的,并将 Navbar 组件渲染为子组件。 将 state 的 name 属性向下传递给子组件,然后在 h1 中显示该 name ,h1 是 Navbar render方法的一部分。 name 应该显示在文本 Hello, my name is: 后面。

class MyApp extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: 'CamperBot'
    }
  }
  render() {
    return (
       
{/* 修改这行下面的代码 */} {/* 修改这行上面的代码 */}
); } }; class Navbar extends React.Component { constructor(props) { super(props); } render() { return (
{/* 修改这行下面的代码 */}

Hello, my name is:{this.props.name}

{/* 修改这行上面的代码 */}
); } };

5.8 传递回调作为 Props

可以将 state 作为 props 传递给子组件,但不仅限于传递数据。 也可以将函数或在 React 组件中定义的任何方法传递给子组件。 这就是子组件与父组件交互的方式。 可以把方法像普通 prop 一样传递给子组件, 它会被分配一个名字,可以在子组件中的 this.props 下访问该方法的名字。

代码编辑器中列出了三个组件。 MyApp 是父组件,GetInput 和RenderInput 是它将要渲染的子组件。 将 GetInput 组件添加到 MyApp 的 render 方法,然后将 MyApp 的 state 中的 inputValue 传入名为 input 的 prop。 还要创建一个名为 handleChange 的 prop,并将输入处理程序 handleChange 传递给它。

接下来,将 RenderInput 添加到 MyApp 中的 render 方法中,然后创建一个名为 input 的 prop,并将 state 中的 inputValue 传递给它。 完成后,可以在 GetInput 组件中的 input 字段中键入内容,然后该组件通过 props 调用其父组件中的处理函数方法。 这将更新处于父组件 state 中的 input,该 input 将作为 props 传递给两个子组件。 观察数据如何在组件之间流动,以及单一数据源如何保持父组件state。 诚然,这个示例有点做作,但是应该能用来说明数据和回调是如何在 React 组件之间传递的。

class MyApp extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      inputValue: ''
    }
    this.handleChange = this.handleChange.bind(this);
  }
  handleChange(event) {
    this.setState({
      inputValue: event.target.value
    });
  }
  render() {
    return (
       
{ /* 修改这行下面的代码 */ } { /* 修改这行上面的代码 */ }
); } }; class GetInput extends React.Component { constructor(props) { super(props); } render() { return (

Get Input:

); } }; class RenderInput extends React.Component { constructor(props) { super(props); } render() { return (

Input Render:

{this.props.input}

); } };

6 小例子 

6.1 一个简单的计数器

Counter 组件跟踪 state 中的 count 值。 有两个按钮分别调用 increment() 和 decrement() 方法。

编写这些方法,使计数器值在单击相应按钮时增加或减少 1。 另外,创建一个 reset() 方法,当单击 reset 按钮时,把计数设置为 0。

注意: 确保没有修改按钮的 className。 另外,请记住在构造函数中为新创建的方法添加必要的绑定。

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
    // 修改这行下面的代码
    this.increment=this.increment.bind(this);
    this.decrement=this.decrement.bind(this);
    this.reset=this.reset.bind(this);
    // 修改这行上面的代码
  }
  // 修改这行下面的代码
  increment() {
    this.setState(state=>({
      count:state.count+1
    }));
  }
  decrement() {
    this.setState(state=>({
      count:state.count-1
    }));
  }
  reset() {
    this.setState(state=>({
      count:0
    }));
  }
  // 修改这行上面的代码
  render() {
    return (
      

Current Count: {this.state.count}

); } };

6.2 创建一个可以控制的输入框

应用程序可能在 state 和渲染的 UI 之间有更复杂的交互。 例如,用于文本输入的表单控件元素(如 input 和 textarea)在用户键入时在 DOM 中维护自己的 state。 通过 React,可以将这种可变 state 转移到 React 组件的 state 中。 用户的输入变成了应用程序 state 的一部分,因此 React 控制该输入字段的值。 通常,如果 React 组件具有用户可以键入的输入字段,那么它将是一个受控的输入表单。

代码编辑器具有一个名为 ControlledInput 的组件框架,用于创建受控的 input 元素。 组件的 state 已经被包含空字符串的 input 属性初始化。 此值表示用户在 input 字段中键入的文本。

首先,创建一个名为 handleChange() 的方法,该方法具有一个名为 event 的参数。 方法被调用时,它接收一个 event 对象,该对象包含一个来自 input 元素的字符串文本。 可以使用方法内的 event.target.value 来访问这个字符串。 用这个新字符串更新组件的stateinput属性。

在 render 方法中的 h4 标签之上创建 input 元素。 添加一个 value 属性,使其等于组件 state 的 input 属性。 然后将 onChange() 事件处理程序设置到 handleChange() 方法中。

在输入框中键入时,文本由 handleChange() 方法处理,文本被设置为本地 state 中的 input 属性,并渲染在页面上的 input 框中。 组件 state 是输入数据的唯一真实来源。

最后,不要忘记在构造函数中添加必要的绑定。

class ControlledInput extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      input: ''
    };
    // 修改这行下面的代码
    this.handleChange=this.handleChange.bind(this);
    // 修改这行上面的代码
  }
  // 修改这行下面的代码
  handleChange(event) {
    this.setState({
      input:event.target.value
    })
  }
  // 修改这行上面的代码
  render() {
    return (
      
{ /* 修改这行下面的代码 */} { /* 修改这行上面的代码 */}

Controlled Input:

{this.state.input}

); } };

6.3 创建一个可以控制的表单

MyForm 组件中是一个带有提交处理程序的空 form 元素, 提交处理程序将在提交表单时被调用。

我们增加了一个提交表单的按钮。 可以看到它的 type 被设置为 submit,表明它是控制表单提交的按钮。 在 form 中添加 input 元素,并像上个挑战一样设置其 value 和 onChange() 属性。 然后,应该完成 handleSubmit 方法,以便将组件 state 属性 submit 设置为本地 state 下的当前输入值。

注意: 还必须在提交处理程序中调用 event.preventDefault(),以防止将会刷新网页的默认的表单提交行为。 为了便于学员操作,默认行为在这里被禁用,以防止重置挑战的代码。

最后,在 form 元素之后创建一个 h1 标签,该标签从组件的 state 渲染 submit 的值。 然后,可以在表单中键入任何内容,然后单击按钮(或按 enter 键),输入会渲染到页面上。

class MyForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      input: '',
      submit: ''
    };
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }
  handleChange(event) {
    this.setState({
      input: event.target.value
    });
  }
  handleSubmit(event) {
    // 修改这行下面的代码
    event.preventDefault();
    this.setState({
      submit:this.state.input
    });
    // 修改这行上面的代码
  }
  render() {
    return (
      
{/* 修改这行下面的代码 */} {/* 修改这行上面的代码 */}
{/* 修改这行下面的代码 */}

{this.state.submit}

{/* 修改这行上面的代码 */}
); } }

7 其他 

7.1 使用生命周期方法:componentWillMount

React 组件有几种特殊方法,可以在组件生命周期的特定点执行操作。 这些称为生命周期方法或生命周期钩子,允许在特定时间点捕获组件。 这可以在渲染之前、更新之前、接收 props 之前、卸载之前等等。 以下是一些主要生命周期方法的列表: componentWillMount() componentDidMount() shouldComponentUpdate() componentDidUpdate() componentWillUnmount() 接下来的几节课将讲述这些生命周期方法的一些基本用例。

注意: componentWillMount 生命周期方法会在版本 16.X 废弃在版本 17 移除。

当组件被挂载到 DOM 时,componentWillMount() 方法在 render() 方法之前被调用。 在componentWillMount()中将一些内容记录到控制台 -- 可能需要打开浏览器控制台以查看输出。

7.2 使用生命周期方法:componentDidMount

某些时候,大多数 web 开发人员需要调用 API 接口来获取数据。 如果正在使用 React,知道在哪里执行这个动作是很重要的。

React 的最佳实践是在生命周期方法 componentDidMount() 中对服务器进行 API 调用或任何其它调用。 将组件装载到 DOM 后会调用此方法。 此处对 setState() 的任何调用都将触发组件的重新渲染。 在此方法中调用 API 并用 API​​ 返回的数据设置 state 时,一旦收到数据,它将自动触发更新。

componentDidMount() 中有一个模拟 API 调用。 它在 2.5 秒后设置 state,以模拟调用服务器检索数据。 本示例请求站点的当前活动用户总数。 在 render 方法中,把 activeUsers 渲染到文字 Active Users: 后的 h1 标签中。 观看预览中发生的事情,随意更改超时时间以查看不同的效果。

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      activeUsers: null
    };
  }
  componentDidMount() {
    setTimeout(() => {
      this.setState({
        activeUsers: 1273
      });
    }, 2500);
  }
  render() {
    return (
      
{/* 修改这行下面的代码 */}

Active Users: {this.state.activeUsers}

{/* 修改这行上面的代码 */}
); } }

7.3 添加事件侦听器

componentDidMount() 方法也是添加特定功能所需的任何事件监听器的最佳位置。 React 提供了一个合成事件系统,它封装了浏览器中的事件系统。 这意味着,不管用户用的是什么浏览器,合成事件系统的行为都完全相同 -- 即使不同浏览器之间的本地事件的行为可能不同。

之前已经接触了一些合成事件处理程序,如onClick()。 React 的合成事件系统非常适合用于在 DOM 元素上管理的大多数交互。 但是,如果要将事件处理程序附加到 document 或 window 对象,则必须直接执行此操作。

在 componentDidMount() 方法中为 keydown 事件添加事件监听器,并让这些事件触发回调 handleKeyPress()。 可以使用 document.addEventListener(),它将事件(用引号括起来)作为第一个参数,将回调作为第二个参数。

然后,在 componentWillUnmount() 中移除相同的事件监听器。 可以把相同的参数传递给 document.removeEventListener()。 在卸载和销毁 React 组件之前,最好在这个生命周期方法中对它们进行清理。 移除事件监听器就是这样一个清理操作的例子。

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      message: ''
    };
    this.handleEnter = this.handleEnter.bind(this);
    this.handleKeyPress = this.handleKeyPress.bind(this);
  }
  // 修改这行下面的代码
  componentDidMount() {
    document.addEventListener('keydown',this.handleKeyPress);
  }
  componentWillUnmount() {
    document.removeEventListener('keydown',this.handleKeyPress);
  }
  // 修改这行上面的代码
  handleEnter() {
    this.setState((state) => ({
      message: state.message + 'You pressed the enter key! '
    }));
  }
  handleKeyPress(event) {
    if (event.keyCode === 13) {
      this.handleEnter();
    }
  }
  render() {
    return (
      

{this.state.message}

); } };

7.4 使用 shouldComponentUpdate 优化重新渲染

到目前为止,如果任何组件接收到新的 state 或新的 props,它会重新渲染自己及其所有子组件。 这通常是好的。 但是 React 提供了一种生命周期方法,当子组件接收到新的 state 或 props 时,可以调用该方法,并特别声明组件是否应该更新。 这个方法就是 shouldComponentUpdate(),它将 nextProps 和 nextState 作为参数。

这种方法是优化性能的有效方法。 例如,默认行为是,当组件接收到新的 props 时,即使 props 没有改变,它也会重新渲染。 可以通过使用 shouldComponentUpdate() 比较 props 来防止这种情况发生。 该方法必须返回一个 boolean(布尔值),该值告诉 React 是否更新组件。 可以比较当前的 props(this.props)和下一个 props(nextProps),以确定你是否需要更新,并相应地返回 true 或 false

将 shouldComponentUpdate() 方法添加到名为 OnlyEvens 的组件中。 目前,该方法返回 true,因此每次收到新的 props 时,OnlyEvens 都会重新渲染。 修改该方法,以便 OnlyEvens 仅在其新 props 的 value 为偶数时更新。 单击 Add 按钮,在触发其他生命周期钩子时,在浏览器控制台中查看事件的顺序。

class OnlyEvens extends React.Component {
  constructor(props) {
    super(props);
  }
  shouldComponentUpdate(nextProps, nextState) {
    console.log('Should I update?');
    // 修改这行下面的代码
    if(nextProps.value % 2 == 0) {
      return true;
    }else {
      return false;
    }
    // 修改这行上面的代码
  }
  componentDidUpdate() {
    console.log('Component re-rendered.');
  }
  render() {
    return 

{this.props.value}

; } } class Controller extends React.Component { constructor(props) { super(props); this.state = { value: 0 }; this.addValue = this.addValue.bind(this); } addValue() { this.setState(state => ({ value: state.value + 1 })); } render() { return (
); } }

7.5 内联样式

还有其他复杂的概念可以为 React 代码增加强大的功能。 但是,你可能会想知道更简单的问题,比如:如何对在 React 中创建的 JSX 元素添加样式。 你可能知道,由于将 class 应用于 JSX 元素的方式与 HTML 中的使用并不完全相同。

如果从样式表导入样式,它就没有太大的不同。 使用 className 属性将 class 应用于 JSX 元素,并将样式应用于样式表中的 class。 另一种选择是使用内联样式,这在 ReactJS 开发中非常常见。

将内联样式应用于 JSX 元素,类似于在 HTML 中的操作方式,但有一些 JSX 差异。 以下是 HTML 中内联样式的示例:

Mellow Yellow

JSX 元素使用 style 属性,但是鉴于 JSX 的编译方式,不能将值设置为 string(字符串)。 相反,你应该将其设置为等于JavaScript object 。 如下所示:

Mellow Yellow

注意到如何驼峰拼写 fontSize 属性了吗? 这是因为 React 不接受样式对象中的 kebab-case 键。 React 将在 HTML 中为应用正确的属性名称。

在代码编辑器中给 div 添加一个 style 属性,将文本颜色设置为红色,字体大小设置为 72px

请注意,可以选择将字体大小设置为数字,省略单位 px,或者将其写为 72px

7.6 使用 && 获得更简洁的条件

if/else 语句在上一次挑战中是有效的,但是有一种更简洁的方法可以达到同样的结果。 假设正在跟踪组件中的几个条件,并且希望根据这些条件中的每一个来渲染不同的元素。 如果你写了很多 else if 语句来返回稍微不同的 UI,你可能会写很多重复代码,这就留下了出错的空间。 相反,你可以使用 && 逻辑运算符以更简洁的方式执行条件逻辑。 这是完全可行的,因为你希望检查条件是否为 true。如果是,则返回一些标记。 下面是一个示例:

{condition && 

markup

}

如果 condition 为 true,则返回标记。 如果条件为 false ,则在评估 condition 后操作将立即返回 false,并且不返回任何内容。 可以将这些语句直接包含在 JSX 中,并通过在每个条件后面写 && 来将多个条件串在一起。 这允许你在 render() 方法中处理更复杂的条件逻辑,而无需重复大量代码。

再来看看前面的示例,h1 还是在 display 为 true 时渲染,但使用 && 逻辑运算符代替 if/else 语句。

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      display: true
    }
    this.toggleDisplay = this.toggleDisplay.bind(this);
  }
  toggleDisplay() {
    this.setState(state => ({
      display: !state.display
    }));
  }
  render() {
    // 修改这行下面的代码
    return (
       
{this.state.display &&

Displayed!

}
); } };

7.7 使用三元表达式进行条件渲染

在继续使用动态渲染技术之前,还有最后一种方法可以渲染想要的东西,它使用内置的 JavaScript 条件:三元运算符。 三元运算符经常被用作 JavaScript 中 if/else 语句的缩写。 它们不像传统的 if/else 语句那样强大,但是在 React 开发人员中非常流行, 原因之一就是 JSX 的编译原理,if/else 语句不能直接插入到 JSX 代码中。 可能你在前几个挑战就注意到了这一点——当需要 if/else 语句时,它总是在 return 语句的外面。 如果想在 JSX 中实现条件逻辑,三元表达式是一个很好的选择。 回想一下,三元运算符有三个部分,但是可以将多个三元表达式组合在一起。 以下是基本语法:

condition ? expressionIfTrue : expressionIfFalse;

代码编辑器在 CheckUserAge 组件的 render() 方法中定义了三个常量, 它们分别是 buttonOnebuttonTwo 和 buttonThree。 每个都分配了一个表示按钮元素的简单 JSX 表达式。 首先,使用 input 和 userAge 初始化 CheckUserAge 的 state,并将其值设置为空字符串。

一旦组件将信息渲染给页面,用户应该有一种方法与之交互。 在组件的 return 语句中,设置一个实现以下逻辑的三元表达式:当页面首次加载时,将提交按钮 buttonOne 渲染到页面。 然后,当用户输入年龄并点击该按钮时,根据年龄渲染不同的按钮。 如果用户输入的数字小于18,则渲染buttonThree。 如果用户输入的数字大于或等于18,则渲染buttonTwo

const inputStyle = {
  width: 235,
  margin: 5
};

class CheckUserAge extends React.Component {
  constructor(props) {
    super(props);
    // 修改这行下面的代码
    this.state={
      input:'',
      userAge:''
    };
    // 修改这行上面的代码
    this.submit = this.submit.bind(this);
    this.handleChange = this.handleChange.bind(this);
  }
  handleChange(e) {
    this.setState({
      input: e.target.value,
      userAge: ''
    });
  }
  submit() {
    this.setState(state => ({
      userAge: state.input
    }));
  }
  render() {
    const buttonOne = ;
    const buttonTwo = ;
    const buttonThree = ;
    return (
      

Enter Your Age to Continue


{/* 修改这行下面的代码 */} {this.state.userAge===''? buttonOne : this.state.userAge <18 ? buttonThree : buttonTwo} {/* 修改这行上面的代码 */}
); } }

7.8 根据 Props 有条件地渲染

到目前为止,已经看到了如何使用 if/else&&和三元运算符(condition ? expressionIfTrue : expressionIfFalse)对渲染什么和何时渲染做出有条件的判定。 然而,还有一个重要的话题需要讨论,将这些概念中的任何一个或所有概念与另一个强大的 React 功能 props 结合起来。 使用 props 有条件地渲染代码在 React 开发人员中很常见——也就是说:他们使用给定 prop 的值来自动决定渲染什么。

在这个挑战中,将设置一个子组件来根据 props 做出渲染决定。 可以使用三元运算符,但是可以看到过去几个挑战中涵盖的其他几个概念在这种情况下可能同样有用。

代码编辑器有两个部分为你定义的组件:一个名为 GameOfChance 的父组件和一个名为 Results 的子组件。 它们被用来创建一个简单的游戏,用户按下按钮来看它们是赢还是输。

首先,需要一个简单的表达式,每次运行时都会随机返回一个不同的值。 可以使用 Math.random()。 每次调用此方法时,此方法返回 0(包括)和 1(不包括)之间的值。 因此,对于50/50的几率,请在表达式中使用 Math.random() >= .5。 从统计学上讲,这个表达式有 50% 的几率返回 true,另外 50% 返回 false。 在第 render 方法里,用此表达式替换 null 以完成变量声明。

现在了一个表达式,可以使用该表达式在代码中做出随机决策。 接下来,需要实现此功能。 将 Results 组件渲染为 GameOfChance 的子 组件,并将 expression 作为名为 fiftyFifty 的 prop 传入 。 在 Results 组件中,编写一个三元表达式来渲染 h1 元素的文本。GameOfChance 传来的 prop fiftyFifty 来决定渲染文本 You Win! 还是 You Lose!。 最后,确保 handleClick() 方法正确计算每个回合,以便用户知道他们玩过多少次。 这也可以让用户知道组件实际上已经更新,以防他们连续赢两次或输两次时自己不知道。

class Results extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    {/* 修改这行下面的代码 */}
    return 

{this.props.fiftyFifty ? 'you win!':'You Lose!'}

; {/* 修改这行上面的代码 */} } } class GameOfChance extends React.Component { constructor(props) { super(props); this.state = { counter: 1 }; this.handleClick = this.handleClick.bind(this); } handleClick() { this.setState(state => { // 完成 return 语句 return { counter: this.state.counter+1 } }); } render() { const expression = Math.random() >= 0.5 ? true : false; // 修改这一行 return (
{/* 修改这行下面的代码 */} {/* 修改这行上面的代码 */}

{'Turn: ' + this.state.counter}

); } }

7.9 使用 Array.map() 动态渲染元素

条件渲染很有用,但是可能需要组件来渲染未知数量的元素。 通常在响应式编程中,程序员在应用程序运行时之前无法知道其 state,因为这在很大程度上取决于用户与该程序的交互。 程序员需要提前编写代码来正确处理未知状态。 在 React 中使用 Array.map() 阐明了这个概念。

例如,创建一个简单的“To Do List”应用程序。 作为程序员,你无法知道用户可能在其列表中有多少项。 需要设置组件,以便在使用该程序的人决定今天今日待办事项之前动态渲染正确数量的列表元素。

代码编辑器完成了 MyToDoList 组件的大部分设置。 如果完成了受控表单挑战,这些代码中的一些应该看起来很熟悉。 你会注意到一个 textarea 和一个 button,以及一些跟踪它们状态的方法,但是页面当前还没有任何东西被渲染。

在 constructor 中,创建一个 this.state 对象并定义两个 state:userInput 应该初始化为空字符串,toDoList 应该初始化为空数组。 接下来,删除 items 变量旁边 render() 方法中的注释。 取而代之的是,将存储在组件内部 state 中的 toDoList 数组一一遍历并相应的动态呈现 li 元素中。 尝试在 textarea 中输入 eat, code, sleep, repeat,然后点击按钮,看看会发生什么。

注意: 像这样的映射操作创建的所有兄弟子元素都需要提供唯一的 key 属性。 别担心,这是下一个挑战的主题。

const textAreaStyles = {
  width: 235,
  margin: 5
};

class MyToDoList extends React.Component {
  constructor(props) {
    super(props);
    // 修改这行下面的代码
    this.state = {
      userInput:'',
      toDoList:[]
    };
    // 修改这行上面的代码
    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleChange = this.handleChange.bind(this);
  }
  handleSubmit() {
    const itemsArray = this.state.userInput.split(',');
    this.setState({
      toDoList: itemsArray
    });
  }
  handleChange(e) {
    this.setState({
      userInput: e.target.value
    });
  }
  render() {
    const items = this.state.toDoList.map(i=>
  • {i}
  • ); // 修改这一行 return (