文档章节

React学习(2)——状态、事件与动态渲染

随风溜达的向日葵
 随风溜达的向日葵
发布于 2016/11/29 22:29
字数 3662
阅读 736
收藏 7

本文记录了在官网学习如何使用JSX+ES6开发React的过程。 

全文共分为3篇内容:

  1. JSX语法与React组件
  2. 状态、事件与动态渲染
  3. 列表、键值与表单

 

    扩展:webpack搭建React开发环境

组件状态和生命周期

    上一篇文章最后说明了组件传入的参数必须是只读的,但是在丰富的前端应用中,页面样式是时时刻刻会发生变化的。在前面的章节中介绍了一个时钟的例子,通过重复调用ReactDOM.render() 来渲染组件:

function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(
    element,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);

    现在介绍另外一种方法实现一个时钟组件,让其真正的具有封装和可复用特性。我们先设计一个时钟类的基本结构:

function Clock(props) {
  return (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {props.date.toLocaleTimeString()}.</h2>
    </div>
  );
}

function tick() {
  ReactDOM.render(
    <Clock date={new Date()} />,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);

测试代码

    然而,现在的Clock只是实现了显示当前时间而已,他需要使用tick反复的调用 ReactDOM.render() 实现Dom渲染。

    按照封装的概念,我们更需要像下面这样来使用一个时钟组件:

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

    React组件提供了一个状态量(state)来实现自我状态的控制。

    state和props类似,但是他是组件内部私有的变量,而且完全由组件自己控制。

    在本文前面已经提到,如果使用ES6风格的“类”(class)创建组件,可以提供很多额外的特性,state一般通过class来实现。

-----------------------------------------------

    附注:

    可能很多一直从事前端开发的童鞋并不太关注面向对象的概念,而使用Java/C++这一类结构化语言的童鞋应该很容易理解后文中将要提到的 类、构造函数、成员变量、成员方法、继承等概念。为了便于阅读这里列举出了中文概念和代码中属于的对照表,帮助大家有效的对照代码理解:

    类: class

    对象:object

    实例:instance

    功能函数:function

    成员方法:method

    构造方法(构造函数):constructor

--------------------------------------------------

用class替换function来创建一个组件

    使用class替换function创建一个组件只需要以下五个步骤。

  1. 创建一个和function一样名称的class并且继承React.componet。
  2. 在class中增加一个名为render()的方法。
  3. 将function中的代码移动到render()方法中。
  4. 在render中用this.props替换props参数。
  5. 删除已经没用的function。

    下面的代码是用class创建一个组件:

     ES6语法:

class Clock extends React.Component {
    render() {
        return (
            <div>
                <h1>Hello, world!</h1>
                <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
            </div>
        );
    }
}

    JavaScript基本语法:

var Clock = React.createClass({
    render: function () {
        return (
            <div>
                <h1>Hello, world!</h1>
                <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
            </div>
        );
    }
});

测试代码

    使用class可以让我们为组件添加更多的属性。

向class中增加本地的state

    下面将展示如何使用组件的state特性。

    1.将render()中 this.props.date 替换成 this.state.date:

class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        //使用state来定义变量
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2> 
        // ——————
      </div>
    );
  }
}

    2.创建一个constructor,并在constructor中初始化this.state:

    ES6语法:

class Clock extends React.Component {
  //构造方法
  constructor(props) {
    //构造父类
    super(props);
    //初始化this.state
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

    JavaScript语法:

var Clock = React.createClass({
    //官网的最新文档已经说明在ES6中使用构造方法来替换getInitialState初始化state
    getInitialState: function () {
        return {date: new Date()};
    },
    render: function () {
        return (
            <div>
                <h1>Hello, world!</h1>
                <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
            </div>
        );
    }
});

    在构造方法中可以获取到当前组件的属性值props,因此我们可以在此将props赋值给state。

    3.移除ReactDOM.render渲染组件时使用的属性:

ReactDOM.render(
  <Clock />,//移除date={new Date()}的属性
  document.getElementById('root')
);

测试代码

向类中增加事件方法(Lifecycle Methods)

    在一个包含了很多组件的系统中,组件被创建或销毁时进行资源管理是一项非常重要的工作。在React中提供了“mounting”(安装)方法,它会在组件被渲染到Dom之前会被调用。而“unmounting”(卸载)方法会在组件被从Dom删除之前调用。

    我们可以在class中定义多种method来获取各种事件,如下例:

    ES6语法:

class Clock extends React.Component {
    constructor(props) {
        super(props);
        this.state = {date: new Date()};
    }
    componentDidMount() {//组件完成Dom渲染时会被调用

    }
    componentWillUnmount() {//组件从Dom删除之前会被调用

    }
    render() {
        return (
            <div>
                <h1>Hello, world!</h1>
                <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
            </div>
        );
    }
}

    JavaScript基本语法:

var Clock = React.createClass({
    getInitialState: function () {
        return {date: new Date()};
    },
    componentDidMount:function() {

    },
    componentWillUnmount:function() {

    },
    render: function () {
        return (
            <div>
                <h1>Hello, world!</h1>
                <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
            </div>
        );
    }
});

    在代码中componentDidMount()方法是在组件被渲染到Dom中后会被调用,这里最适合创建一个时间计数功能:

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

    创建了计数功能的同时,我们将一个timerID作为一个变量存储到this中,this表示当前组件的一个实例(instance),我们可以将任何和组件相关的变量都存储到this中,以便在所有方法中使用(学Java/C++的童鞋,我不多说,这就是成员变量和成员函数)。因此,当组件将要被销毁时,我们应该移除这个时间计数器:

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

     最后,创建一个tick()方法用来持续更新时间。在tick()方法中会使用state来更新组件,下面是这个组件的完整代码:

    ES6语法:

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() {//使用父类React.Component的setState()方法更新state:设置当前时间
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

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

JavaScript基本语法:

var Clock = React.createClass({
    getInitialState: function () {
        return {date: new Date()};
    },
    componentDidMount: function () {
        this.timerID = setInterval(function () {
            this.tick();
        }, 1000);
    },
    componentWillUnmount: function () {
        clearInterval(this.timerID);
    },
    tick:function () {
        this.setState({
            date: new Date()
        });
    },
    render: function () {
        return (
            <div>
                <h1>Hello, world!</h1>
                <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
            </div>
        );
    }
});

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

测试代码

    让我们在重现一下组件到底做了什么,并梳理类中每一个方法调用的顺序:

  1. 当调用 ReactDOM.render() 时,我们传递了<Clock/>参数。React会调用Clock组件的构造函数(constructor)。由于组件需要显示当前的时间,在构造函数中使用一个时间对象的实例赋值给了state:this.state = {date: new Date()};
  2. 随后React会调用Clock组件的 render() 方法。在 render() 方法中会返回一个Dom结构,这个结构告诉React应该在浏览器中显示什么样的内容。render()返回之后,React会向浏览器渲染这个Dom。
  3. 在React向浏览器渲染Dom之后, componentDidMount() 会被调用,在这个方法中,组件使用 setInterval() 方法创建了一个timer实例,并定期调用 tick() 方法。
  4. 浏览器每秒都会调用 tick() 方法,这个方法中组件调用父类的 setState() 方法来定期更新页面上展示的时间数据。由于继承自父类React.Component,每次调用 setState() 方法都会更新this.state 的值,并且告知React状态发生了改变,React会再次使用 render() 方法使用最新的state值更新对应的Dom。
  5. 当Clock组件被从Dom移除时,React会调用组件的 componentWillUnmount() 方法移除timer。

正确的使用state

    在使用 setState() 方法时有三点需要了解:

    切勿直接修改state

    例如使用下面的方法组件将不会重新渲染:

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

    必须使用 setState() 来更新组件:

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

    仅仅只能在构造函数中给this.state赋值。

    state异步更新

    React在某些情况下会一次性更新多次setState调用,而不是每次调用setState都会直接更新。因此this.props或this.state可能会出现异步更新的情况,因此某些累计或累加型的运算切勿直接使用setState。例如下面的这个例子:

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

    有时需要使用counter记录累加的结果,但是在某些时候counter并没有被更新。为了解决这个问题,React提供了一个setState的重载方法:setState(function(prevState,props)),例如:

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

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

    使用 setState的 重载方法后,在function中接受的第一个参数是前一个状态值,而第二个参数是当前props值。

    state的更新会被合并

    当调用setState时,React会将上一次更新的值和本次更新的值进行合并。例如在state中包含相互独立的变量:

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
      });
    });
  }

数据单向性

    无论父组件还是子组件,都无法知晓其他组件的状态,只能在内部封装中调用 setState() 方法。

    父组件可以将state值作为一个属性(props)传递给子组件,如下:

<FormattedDate date={this.state.date} />

function FormattedDate(props) {
  return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}

测试代码

    FormattedDate组件可以通过自己的props获取date值,但是它并不知道这个值是Clock的state值。
    数据单向性保证所有的状态值(state)只能在组件内部使用(封装特性),而所有组件只能影响它内部派生的组件。

    组件是相互独立的,即使是同一个组件,在不同的地方使用会产生不同的实例。看下面这个例子,在App组件中使用了三个Clock组件:

function App() {
  return (
    <div>
      <Clock />
      <Clock />
      <Clock />
    </div>
  );
}

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

测试代码

    每一个<Clock />标签都会创建它自己的timer并独立更新。

事件处理

    React中的事件处理和Dom的事件处理非常相似。但是还是存在某些不同之处:

  1. React的事件命名规范必须遵守“驼峰原则”。
  2. JSX标签中,我们传递一个function作为事件的处理方法,而不是一个字符串。

    例如在html中,处理一个事件:

<button onclick="activateLasers()">
  Activate Lasers
</button>

    而在React中:

<button onClick={activateLasers}>
  Activate Lasers
</button>

    还有一个区别是,在React 中不能在函数中返回false来阻止React的默认行为,必须明确调用 preventDefault 方法来阻止某些行为。例如在html中,在a标签中为了防止a标签的默认行为打开一个页面,我们可以这样来写:

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

    而在React实现这个功能,需要这样编码:

function ActionLink() {//定义一个组件
  function handleClick(e) {
    e.preventDefault();//阻止冒泡行为
    console.log('The link was clicked.');
  }

  return (
    <a href="#" onClick={handleClick}>
      Click me
    </a>
  );
}

    在这个例子中,e是一个合成的事件对象实例(event对象),React根据W3C标准合成了事件对象,因此我们不必担心浏览器之间的兼容性问题。可以参阅官网 SyntheticEvent 了解事件对象的细节。

    在使用React时,注册对某个Dom对象的事件监听不需要调用addEventListener 方法,仅仅需要在元素被渲染时(组件的render方法中)提供监听即可。

    当我们创建一个组建时,最通常的方法是使用一个处理器(handle)来处理对应的事件,看下面这个例子:

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

    // 必须要使用bind方法确保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('root')
);

测试代码

根据条件渲染

    在React中可以创建各种各样的组件以满足不同的需求。可以在组件中进行条件判断来觉得组件最终的呈现效果。我们可以按照JavaScript的条件判断方法来实现根据条件渲染组件:

    首先创建2个组件:

function UserGreeting(props) {
  return <h1>Welcome back!</h1>;
}

function GuestGreeting(props) {
  return <h1>Please sign up.</h1>;
}

    然后我们根据输入的参数条件来决定如何输出:

function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  if (isLoggedIn) {
    return <UserGreeting />;
  }
  return <GuestGreeting />;
}

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

测试代码

元素变量

    可以使用一个变量来存储元素,并根据条件变化来改变渲染的效果。下面是一个登入和登出的例子:

function LoginButton(props) {
  return (
    <button onClick={props.onClick}>
      Login
    </button>
  );
}

function LogoutButton(props) {
  return (
    <button onClick={props.onClick}>
      Logout
    </button>
  );
}
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>
    );
  }
}

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

测试代码

    在例子中,首先创建了2个无状态的按钮——login、logout。然后在LoginControl中设定了“this.state = {isLoggedIn: false};”。前面我们已经提到过,每当调用setState方法设置状态时,render方法都会被调用并重新渲染Dom,因此在每次点击按钮后都会根据isLoggedIn的状态来决定显示的内容。

使用&&实现更紧凑的语法

    我们可以使用&&来实现更紧凑的语法。在大括号({})中,我们可以将任何表达式嵌入到JSX语法中。下面的例子我们使用&&逻辑运算符来进行Dom渲染控制:

function Mailbox(props) {
  const unreadMessages = props.unreadMessages;
  return (
    <div>
      <h1>Hello!</h1>
      {unreadMessages.length > 0 &&
        <h2>
          You have {unreadMessages.length} unread messages.
        </h2>
      }
    </div>
  );
}

const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
  <Mailbox unreadMessages={messages} />,
  document.getElementById('root')
);

测试代码

   true && expression 是JavaScript中的逻辑表达式,&&前为真时&&之后的判定表达式一定会执行,而&&前为假时后续判定将不会执行。因此,只要 unreadMessages.length 为真,那么之后的JSX语句就会执行。

使用?:;三目表达式

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

    这个例子使用了condition ? true : false表达式。我们还可以将其运用到比较长的语句中:

render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      {isLoggedIn ? (
        <LogoutButton onClick={this.handleLogoutClick} />
      ) : (
        <LoginButton onClick={this.handleLoginClick} />
      )}
    </div>
  );
}

隐藏组件

    有时候,我们需要组件隐藏自己不显示到Dom中。我们可以通过render方法返回null来实现这个效果:

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

  return (
    <div className="warning">
      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 ? 'Hide' : 'Show'}
        </button>
      </div>
    );
  }
}

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

    这个例子只要 props.warn 有值就显示警告信息,没有则返回null将其隐藏。

原文地址:http://www.chkui.com/article/react/react_state_event_and_render

© 著作权归作者所有

共有 人打赏支持
随风溜达的向日葵
粉丝 260
博文 71
码字总数 153724
作品 0
广州
其他
加载中

评论(3)

随风溜达的向日葵
随风溜达的向日葵

引用来自“潜水艇-x”的评论

...弄错了,我没有从新定义date。。。:cold_sweat:
:smile:
潜水艇-x
...弄错了,我没有从新定义date。。。:cold_sweat:
潜水艇-x
有个地方写错了,在 用class替换function来创建一个组件 中,,es6语法
this.props.date.toLocaleTimeString()
应该是this.state.date.toLocaleTimeString()
ReactJS学习笔记——组件复合及表单的处理

ReactJS学习笔记——组件复合及表单的处理 React是一个JavaScript库文件,使用它的目的在于能够解决构建大的应用和数据的实时变更。该设计使用JSX允许你在构建标签结构时充分利用JavaScript的...

小米墨客
2016/03/26
3.5K
3
通往全栈工程师的捷径 —— React

首先,我们来看看 React 在世界范围的热度趋势,下图是关键词“房价”和 “React” 在 Google Trends 上的搜索量对比,蓝色的是 React,红色的是房价,很明显,人类对 React 的关注程度已经远...

我家有宝
2016/01/14
37
0
通往全栈工程师的捷径 —— react

通往全栈工程师的捷径 —— react 腾讯Bugly特约作者: 左明 首先,我们来看看 React 在世界范围的热度趋势,下图是关键词“房价”和 “React” 在 Google Trends 上的搜索量对比,蓝色的是 ...

腾讯小伙伴
2015/11/25
2.1K
17
ReactJS学习笔记——生命周期、数据流与事件

ReactJS学习笔记——生命周期、数据流与事件 React是一个JavaScript库文件,使用它的目的在于能够解决构建大的应用和数据的实时变更。该设计使用JSX允许你在构建标签结构时充分利用JavaScrip...

小米墨客
2016/03/21
2.1K
3
React 动态渲染图片,提升用户体验

本文作者:Andrew Wong 翻译原文:http://huziketang.com/blog/posts/detail?postId=58d14215a6d8a07e449fdd2b 英文连接:Improve Your UX by Dynamically Rendering Images via React 市场上......

胡子大哈
2017/03/21
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

配置Spring的注解支持

声明:本栏目所使用的素材都是凯哥学堂VIP学员所写,学员有权匿名,对文章有最终解释权;凯哥学堂旨在促进VIP学员互相学习的基础上公开笔记。 配置Spring的注解支持 以上也提到了使用注解来配...

凯哥学堂
28分钟前
0
0
关于Spring Aop存在的一点问题的思考

在本人前面的文章Spring Aop原理之切点表达式解析中讲解了Spring是如何解析切点表达式的,在分析源码的时候,出现了如下将要讲述的问题,我认为是不合理的,后来本人单纯使用aspectj进行试验...

爱宝贝丶
29分钟前
0
0
JavaScript 概述

JavaScript是面向Web的编程语言。绝大多数现代网站都使用了JavaScript,并且所有的现代Web浏览器——基于桌面系统、游戏机、平板电脑和智能手机的浏览器——均包含了JavaScript解释器。这使得...

Mr_ET
59分钟前
0
0
Java Run-Time Data Areas(Java运行时数据区/内存分配)

Java运行时数据区(内存分配) 本文转载官网 更多相关内容可查看官网 中文翻译可参考 2.5. Run-Time Data Areas The Java Virtual Machine defines various run-time data areas that are use...

lichuangnk
今天
0
0
docker learn :services docker-compose.yml

docker-compose.yml定义了服务的运行参数 version: "3" services: web: # replace username/repo:tag with your name and image details image: hub.c.163.com/dog948453219/friendlyhello d......

writeademo
今天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部