React详解

React的安装和配置

https://zh-hans.reactjs.org/docs/getting-started.html

JSX 简介

const element = 

Hello, world!

;

它被称为 JSX,是一个 JavaScript 的语法扩展。我们建议在 React 中配合使用 JSX,JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式。JSX 可能会使人联想到模版语言,但它具有 JavaScript 的全部功能。
在 JSX 中嵌入表达式

在下面的例子中,我们声明了一个名为 name 的变量,然后在 JSX 中使用它,并将它包裹在大括号中:

const name = 'Josh Perez';
const element = 

Hello, {name}

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

在 JSX 语法中,你可以在大括号内放置任何有效的 JavaScript 表达式。例如,2 + 2,user.firstName 或 formatName(user) 都是有效的 JavaScript 表达式。

在下面的示例中,我们将调用 JavaScript 函数 formatName(user) 的结果,并将结果嵌入到

元素中。

function formatName(user) {
  return user.firstName + ' ' + user.lastName;
}

const user = {
  firstName: 'Harper',
  lastName: 'Perez'
};

const element = (
  

Hello, {formatName(user)}!

); ReactDOM.render( element, document.getElementById('root') );
JSX 也是一个表达式

在编译之后,JSX 表达式会被转为普通 JavaScript 函数调用,并且对其取值后得到 JavaScript 对象。

也就是说,你可以在 if 语句和 for 循环的代码块中使用 JSX,将 JSX 赋值给变量,把 JSX 当作参数传入,以及从函数中返回 JSX:

function getGreeting(user) {
  if (user) {
    return 

Hello, {formatName(user)}!

; } return

Hello, Stranger.

; }
JSX 特定属性

你可以通过使用引号,来将属性值指定为字符串字面量:

const element = 
;

也可以使用大括号,来在属性值中插入一个 JavaScript 表达式:

const element = ;

在属性中嵌入 JavaScript 表达式时,不要在大括号外面加上引号。你应该仅使用引号(对于字符串值)或大括号(对于表达式)中的一个,对于同一属性不能同时使用这两种符号。

JSX 防止注入攻击、

你可以安全地在 JSX 当中插入用户输入内容:

const title = response.potentiallyMaliciousInput;
// 直接使用是安全的:
const element = 

{title}

;

React DOM 在渲染所有输入内容之前,默认会进行转义。它可以确保在你的应用中,永远不会注入那些并非自己明确编写的内容。所有的内容在渲染之前都被转换成了字符串。这样可以有效地防止 XSS(cross-site-scripting, 跨站脚本)攻击。

JSX 表示对象

Babel 会把 JSX 转译成一个名为React.createElement() 函数调用。

以下两种示例代码完全等效:

const element = (
  

Hello, world!

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

React.createElement() 会预先执行一些检查,以帮助你编写无错代码,但实际上它创建了一个这样的对象:

// 注意:这是简化过的结构
const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world!'
  }
};

这些对象被称为 “React 元素”。它们描述了你希望在屏幕上看到的内容。React 通过读取这些对象,然后使用它们来构建 DOM 以及保持随时更新。

元素渲染

元素是构成 React 应用的最小砖块。

元素描述了你在屏幕上想看到的内容。

const element = 

Hello, world

;

与浏览器的 DOM 元素不同,React 元素是创建开销极小的普通对象。React DOM 会负责更新 DOM 来与 React 元素保持一致。

将一个元素渲染为 DOM

假设你的 HTML 文件某处有一个

我们将其称为“根” DOM 节点,因为该节点内的所有内容都将由 React DOM 管理。

想要将一个 React 元素渲染到根 DOM 节点中,只需把它们一起传入 ReactDOM.render():

const element = 

Hello, world

; ReactDOM.render(element, document.getElementById('root'));
更新已渲染的元素

React 元素是不可变对象。一旦被创建,你就无法更改它的子元素或者属性。一个元素就像电影的单帧:它代表了某个特定时刻的 UI。

根据我们已有的知识,更新 UI 唯一的方式是创建一个全新的元素,并将其传入 ReactDOM.render()。

考虑一个计时器的例子:

function tick() {
  const element = (
    

Hello, world!

It is {new Date().toLocaleTimeString()}.

); ReactDOM.render(element, document.getElementById('root')); } setInterval(tick, 1000);

React 只更新它需要更新的部分

React DOM 会将元素和它的子元素与它们之前的状态进行比较,并只会进行必要的更新来使 DOM 达到预期的状态。

你可以使用浏览器的检查元素工具查看上一个例子来确认这一点。
React详解_第1张图片
尽管每一秒我们都会新建一个描述整个 UI 树的元素,React DOM 只会更新实际改变了的内容,也就是例子中的文本节点。

组件 & Props

组件允许你将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思。

组件,从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素。

函数组件与 class 组件

定义组件最简单的方式就是编写 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 元素都只是 DOM 标签:

const element = 
;

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

const element = ;

当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为 “props”。

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

function Welcome(props){
    return 

Hello,{props.name}

} const element = ReactDOM.render(element,document.getElementById('root'))
  • 我们调用 ReactDOM.render() 函数,并传入 作为参数。
  • React 调用 Welcome 组件,并将 {name: ‘Sara’} 作为 props 传入。
  • Welcome 组件将

    Hello, Sara

    元素作为返回值。
    *React DOM 将 DOM 高效地更新为

    Hello, Sara

注意: 组件名称必须以大写字母开头。
React 会将以小写字母开头的组件视为原生 DOM 标签。例如,

代表 HTML 的 div 标签,而 则代表一个组件,并且需在作用域内使用 Welcome。

组合组件

组件可以在其输出中引用其他组件。这就可以让我们用同一组件来抽象出任意层次的细节。按钮,表单,对话框,甚至整个屏幕的内容:在 React 应用程序中,这些通常都会以组件的形式表示。

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

function Welcome(props) {
  return 

Hello, {props.name}

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

通常来说,每个新的 React 应用程序的顶层组件都是 App 组件。但是,如果你将 React 集成到现有的应用程序中,你可能需要使用像 Button 这样的小组件,并自下而上地将这类组件逐步应用到视图层的每一处。

提取组件

将组件拆分为更小的组件。

例如,参考如下 Comment 组件:

function Component(props){
    return (
        
{props.author.name}/
{props.author.name}
{props.text}
{formatDate(props.date)}
) }

该组件用于描述一个社交媒体网站上的评论功能,它接收 author(对象),text (字符串)以及 date(日期)作为 props。

该组件由于嵌套的关系,变得难以维护,且很难复用它的各个部分。因此,让我们从中提取一些组件出来。

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

function Avatar(props){
    return (
        {props.user.name}/
    )
}

Avatar 不需知道它在 Comment 组件内部是如何渲染的。因此,我们给它的 props 起了一个更通用的名字:user,而不是 author。

我们建议从组件自身的角度命名 props,而不是依赖于调用组件的上下文命名。

接下来,我们将提取 UserInfo 组件,该组件在用户名旁渲染 Avatar 组件

function UserInfo(props){
    return (
        
{props.user.name}
) }

进一步简化 Comment 组件:

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

最初看上去,提取组件可能是一件繁重的工作,但是,在大型应用中,构建可复用组件库是完全值得的。根据经验来看,如果 UI 中有一部分被多次使用(Button,Panel,Avatar),或者组件本身就足够复杂(App,FeedStory,Comment),那么它就是一个可提取出独立组件的候选项

Props 的只读性

组件无论是使用函数声明还是通过 class 声明,都决不能修改自身的 props。来看下这个 sum 函数:

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

这样的函数被称为“纯函数”,因为该函数不会尝试更改入参,且多次调用下相同的入参始终返回相同的结果。
相反,下面这个函数则不是纯函数,因为它更改了自己的入参:

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

React 非常灵活,但它也有一个严格的规则:
所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。

当然,应用程序的 UI 是动态的,并会伴随着时间的推移而变化
在不违反上述规则的情况下,state 允许 React 组件随用户操作、网络响应或者其他变化而动态更改输出内容。

State & 生命周期

封装真正可复用的 Clock 组件。它将设置自己的计时器并每秒更新一次。
我们可以从封装时钟的外观开始:


function Clock(props) {
  return (
    

Hello,word!

it is {props.date.toLocaleTimeString()}

) } function tick(){ ReactDOM.render( , document.getElementById('root') ) }

然而,它忽略了一个关键的技术细节:Clock 组件需要设置一个计时器,并且需要每秒更新 UI。

理想情况下,我们希望只编写一次代码,便可以让 Clock 组件自我更新:
我们需要在 Clock 组件中添加 “state” 来实现这个功能。
State 与 props 类似,但是 state 是私有的,并且完全受控于当前组件。

将函数组件转换成 class 组件

通过以下五步将 Clock 的函数组件转成 class 组件:

*创建一个同名的 ES6 class,并且继承于 React.Component。

  • 添加一个空的 render() 方法。
    *将函数体移动到 render() 方法之中。
    *在 render() 方法中使用 this.props 替换 props。
    *删除剩余的空函数声明。
class Clock extends Component{
    render(){
        return(
            

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

) } }

现在 Clock 组件被定义为 class,而不是函数。

每次组件更新时 render 方法都会被调用,但只要在相同的 DOM 节点中渲染 ,就仅有一个 Clock 组件的 class 实例被创建使用。这就使得我们可以使用如 state 或生命周期方法等很多其他特性

向 class 组件中添加局部的 state

我们通过以下三步将 date 从 props 移动到 state 中:

  • 1 把 render() 方法中的 this.props.date 替换成 this.state.date :
  • 2 添加一个 class 构造函数,然后在该函数中为 this.state 赋初值:
    通过以下方式将 props 传递到父类的构造函数中:
constructor(props){
    super(props)
    this.state = {date: new Date()}
}
  • 3 移除 元素中的 date 属性:
ReactDOM.render(
  ,
  document.getElementById('root')
);

完整代码

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

  render() {
    return (
      

Hello, world!

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

); } } ReactDOM.render( , document.getElementById('root') );
将生命周期方法添加到 Class 中

在具有许多组件的应用程序中,当组件被销毁时释放所占用的资源是非常重要的。
当 Clock 组件第一次被渲染到 DOM 中的时候,就为其设置一个计时器。这在 React 中被称为“挂载(mount)”。

同时,当 DOM 中 Clock 组件被删除的时候,应该清除计时器。这在 React 中被称为“卸载(unmount)”。

我们可以为 class 组件声明一些特殊的方法,当组件挂载或卸载时就会去执行这些方法:


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

Hello, world!

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

); } }

这些方法叫做“生命周期方法”。

componentDidMount() 方法会在组件已经被渲染到 DOM 中后运行,所以,最好在这里设置计时器:

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

接下来把计时器的 ID 保存在 this 之中(this.timerID)。

尽管 this.props 和 this.state 是 React 本身设置的,且都拥有特殊的含义,但是其实你可以向 class 中随意添加不参与数据流(比如计时器 ID)的额外字段。

我们会在 componentWillUnmount() 生命周期方法中清除计时器:

 componentWillUnmount(){
      clearInterval(this.timeID)
  }

最后,我们会实现一个叫 tick() 的方法,Clock 组件每秒都会调用它。

使用 this.setState() 来时刻更新组件 state:

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }
  componentDidMount(){
      this.timeID = setInterval(() =>this.tick(), 1000);
  }
  componentWillUnmount(){
      clearInterval(this.timeID)
  }
  tick(){
      this.setState({
          date:new Date()
      })
  }
  render() {
    return (
      

Hello, world!

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

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

这些方法的调用顺序:

  • 1 当 被传给 ReactDOM.render()的时候,React 会调用 Clock 组件的构造函数。因为 Clock 需要显示当前的时间,所以它会用一个包含当前时间的对象来初始化 this.state。我们会在之后更新 state。
  • 2 之后 React 会调用组件的 render() 方法。这就是 React 确定该在页面上展示什么的方式。然后 React 更新 DOM 来匹配 Clock 渲染的输出。
    3 当 Clock 的输出被插入到 DOM 中后,React 就会调用 ComponentDidMount() 生命周期方法。在这个方法中,Clock 组件向浏览器请求设置一个计时器来每秒调用一次组件的 tick() 方法
    4 浏览器每秒都会调用一次 tick() 方法。 在这方法之中,Clock 组件会通过调用 setState() 来计划进行一次 UI 更新。得益于 setState() 的调用,React 能够知道 state 已经改变了,然后会重新调用 render() 方法来确定页面上该显示什么。这一次,render() 方法中的 this.state.date 就不一样了,如此以来就会渲染输出更新过的时间。React 也会相应的更新 DOM。
    5 一旦 Clock 组件从 DOM 中被移除,React 就会调用 componentWillUnmount() 生命周期方法,这样计时器就停止了。
State 的更新可能是异步的

出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。

因为 this.props 和 this.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态。

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

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

要解决这个问题,可以让 setState() 接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数:

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

上面使用了箭头函数,不过使用普通的函数也同样可以:

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

State 的更新会被合并

当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state。

例如,你的 state 包含几个独立的变量:

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

然后你可以分别调用 setState() 来单独地更新它们:

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

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

数据是向下流动的

不管是父组件或是子组件都无法知道某个组件是有状态的还是无状态的,并且它们也并不关心它是函数组件还是 class 组件。

这就是为什么称 state 为局部的或是封装的的原因。除了拥有并设置了它的组件,其他组件都无法访问。

组件可以选择把它的 state 作为 props 向下传递到它的子组件中:


FormattedDate 组件会在其 props 中接收参数 date,但是组件本身无法知道它是来自于 Clock 的 state,或是 Clock 的 props,还是手动输入的:

 function FormattedDate(props){
    return 

it is {props.date.toLocaleTimeString()}

}

这通常会被叫做“自上而下”或是“单向”的数据流。任何的 state 总是所属于特定的组件,而且从该 state 派生的任何数据或 UI 只能影响树中“低于”它们的组件。

如果你把一个以组件构成的树想象成一个 props 的数据瀑布的话,那么每一个组件的 state 就像是在任意一点上给瀑布增加额外的水源,但是它只能向下流动。

在 React 应用中,组件是有状态组件还是无状态组件属于组件实现的细节,它可能会随着时间的推移而改变。你可以在有状态的组件中使用无状态的组件,反之亦然。

事件处理

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

React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
例如,传统的 HTML:


在 React 中略微不同:


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


  Click me

在 React 中,可能是这样的:

function ActionLink(){
    function handleClick(e){
        e.preventDefault()
        console.log('the link was clicked');
        
    }
    return (
        
        Click Me
        
    )
}

在这里,e 是一个合成事件。React 根据 W3C 规范来定义这些合成事件,所以你不需要担心跨浏览器的兼容性问题。React 事件与原生事件不完全相同。如果想了解更多,请查看 SyntheticEvent 参考指南。

使用 React 时,你一般不需要使用 addEventListener 为已创建的 DOM 元素添加监听器。事实上,你只需要在该元素初始渲染的时候添加监听器即可。

当你使用 ES6 class 语法定义一个组件的时候,通常的做法是将事件处理函数声明为 class 中的方法。例如,下面的 Toggle 组件会渲染一个让用户切换开关状态的按钮:

class Toggle extends React.Component{
    constructor(props){
        super(props)
        this.state = {isToggleOn:true}
        //.为了在回调中使用'this' 这个绑定是必不可少的
        this.handleClick = this.handleClick.bind(this)
    }
    handleClick(){
        this.setState({
            isToggleOn:!state.isToggleOn
        })
    }
    render(){
        return (
            
        )
    }
}
ReactDOM.render(,document.getElementById('root'))

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

这并不是 React 特有的行为;这其实与 JavaScript 函数工作原理有关。通常情况下,如果你没有在方法后面添加 (),例如 onClick={this.handleClick},你应该为这个方法绑定 this。

如果觉得使用 bind 很麻烦,这里有两种方式可以解决。如果你正在使用实验性的 public class fields 语法,你可以使用 class fields 正确的绑定回调函数:

class LoggingButton extends React.Component{
    //此语法确保`handleClick`内的`this`已被绑定
    handleClick = ()=>{
        console.log('this is',this);
        
    }
    render(){
        return (
            
        )
    }
}

Create React App 默认启用此语法。

如果你没有使用 class fields 语法,你可以在回调中使用箭头函数:

class LoggingButton extends React.Component {
    handleClick(){
        console.log('this is',this);
    }
    render(){
        //此语法确保`handleClick`内的`this`已被绑定
        return (
            
        )
    }
}

此语法问题在于每次渲染 LoggingButton 时都会创建不同的回调函数。在大多数情况下,这没什么问题,但如果该回调函数作为 prop 传入子组件时,这些组件可能会进行额外的重新渲染。我们通常建议在构造器中绑定或使用 class fields 语法来避免这类性能问题。

向事件处理程序传递参数

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



上述两种方式是等价的,分别通过箭头函数和 Function.prototype.bind 来实现。

在这两种情况下,React 的事件对象 e 会被作为第二个参数传递。如果通过箭头函数的方式,事件对象必须显式的进行传递,而通过 bind 的方式,事件对象以及更多的参数将会被隐式的进行传递。

条件渲染

在 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(,
document.getElementById('root')
)
元素变量

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

观察这两个组件,它们分别代表了注销和登录按钮:

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
        if(isLoggedIn){
            button = 
        }else{
            buttonv = 
        }
        return (
            
{button}
) } } ReactDOM.render(,document.getElementById('root'))

声明一个变量并使用 if 语句进行条件渲染是不错的方式,但有时你可能会想使用更为简洁的语法。
在 JSX 中内联条件渲染的方法

与运算符 &&

通过花括号包裹代码,你可以在 JSX 中嵌入表达式。这也包括 JavaScript 中的逻辑与 (&&) 运算符。它可以很方便地进行元素的条件渲染:

function Mailbox(props){
    const unreadMessage = props.unreadMessage
    return (
        

hello,word

{unreadMessage.length>0&&

You have {unreadMessage.length} unread messages

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

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

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

请注意,返回 false 的表达式会使 && 后面的元素被跳过,但会返回 false 表达式。在下面示例中,render 方法的返回值是

0

render(){
    const count = 0
    return(
        
{count &&

Messages:{count}

}
) }
三目运算符

另一种内联条件渲染的方法是使用 JavaScript 中的三目运算符 condition ? true : false。

在下面这个示例中,我们用它来条件渲染一小段文本

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

同样的,它也可以用于较为复杂的表达式中,虽然看起来不是很直观:

render(){
    const isLoginIn = this.state.isLoginIn
    return (
        
{isLoginIn ? : }
) }

如果条件变得过于复杂,那你应该考虑如何提取组件。

阻止组件渲染

在极少数情况下,你可能希望能隐藏组件,即使它已经被其他组件渲染。若要完成此操作,你可以让 render 方法直接返回 null,而不进行任何渲染。

下面的示例中, 会根据 prop 中 warn 的值来进行条件渲染。如果 warn 的值是 false,那么组件则不会渲染:

function WarningBanner(props){
    if(props.warn){
        return false
    }
    return (
        
Warning
) } class Page extends React.Component{ constructor(props){ super(props) this.state = { showWarning:false } this.handleToggleClick = this.handleToggleClick.bind(this) } handleToggleClick(){ this.setState((state)=>({ showWarning:!state.showWarning })) } render(){ return (
) } } ReactDOM.render(,document.getElementById('root'))

在组件的 render 方法中返回 null 并不会影响组件的生命周期。例如,上面这个示例中,componentDidUpdate 依然会被调用。

列表 & Key

在 Javascript 中如何转化列表。

如下代码,我们使用 map() 函数让数组中的每一项变双倍,然后我们得到了一个新的列表 doubled 并打印出来:

const numbers = [1,2,3,4]
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]
    const listItems = numbers.map((number)=>
        
  • {number}
  • )

    我们把整个 listItems 插入到

    • 元素中,然后渲染进 DOM:

    ReactDom.render(
      {listLitems}
    ,document.getElementById('root'))

    基础列表组件
    通常你需要在一个组件中渲染列表。

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

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

    key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。
    一个元素的 key 最好是这个元素在列表中拥有的一个独一无二的字符串。通常,我们使用数据中的 id 来作为元素的 key:
    当元素没有确定 id 的时候,万不得已你可以使用元素索引 index 作为 key

    如果列表项目的顺序可能会变化,我们不建议使用索引来用作 key 值,因为这样做会导致性能变差,还可能引起组件状态的问题。可以看看 Robin Pokorny 的深度解析使用索引作为 key 的负面影响这一篇文章。如果你选择不指定显式的 key 值,那么 React 将默认使用索引用作为列表项目的 key 值。

    要是你有兴趣了解更多的话,这里有一篇文章深入解析为什么 key 是必须的可以参考。

    用 key 提取组件
    元素的 key 只有放在就近的数组上下文中才有意义。

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

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

    一个好的经验法则是:在 map() 方法中的元素需要设置 key 属性

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

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

      {post.title}

      {post.content}

      }) }
    ) return (
    {sildeBar}
    {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 组件可以读出 props.id,但是不能读出 props.key。

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

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

    JSX 允许在大括号中嵌入任何表达式,所以我们可以内联 map() 返回的结果:

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

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

    表单

    受控组件
    在 HTML 中,表单元素(如、 和 )通常自己维护 state,并根据用户输入进行更新。而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新。
    React 的 state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。
    例如,如果我们想让前一个示例在提交时打印出名称,我们可以将表单写为受控组件:

    class NameForm extends React.Component{
        constructor(props){
            super(props)
            this.state = {value:''}
            this.handleChang= this.handleChang.bind(this)
            this.handleSubmit = this.handleSubmit.bind(this)
        }
        handleChang(e){
            this.setState({
                value:e.target.value
            })
        }
        handleSubmit(event){
            alert('提交的名字是:'+this.state.value)
            event.preventDefault()
        }
        render(){
            return (
                
    ) } }

    由于在表单元素上设置了 value 属性,因此显示的值将始终为 this.state.value,这使得 React 的 state 成为唯一数据源。由于 handlechange 在每次按键时都会执行并更新 React 的 state,因此显示的值将随着用户输入而更新。
    对于受控组件来说,输入的值始终由 React 的 state 驱动。你也可以将 value 传递给其他 UI 元素,或者通过其他事件处理函数重置,但这意味着你需要编写更多的代码。

    textarea 标签

    在 HTML 中, 元素通过其子元素定义其文本:

    
    

    而在 React 中, 使用 value 属性代替。这样,可以使得使用 的表单和使用单行 input 的表单非常类似:

    class EssayForm extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          value: '请撰写一篇关于你喜欢的 DOM 元素的文章.'
        };
    
        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
      }
    
      handleChange(event) {
        this.setState({value: event.target.value});
      }
    
      handleSubmit(event) {
        alert('提交的文章: ' + this.state.value);
        event.preventDefault();
      }
    
      render() {
        return (