美文网首页
React 快速入门

React 快速入门

作者: faremax | 来源:发表于2018-01-21 14:45 被阅读2942次

    本文采用 es6 语法,完全参考 https://reactjs.org/docs/
    本文完全参考 React 官方 Quick Start 部分,除了最后的 thinking-in-react 小节

    安装

    首先你需要点击安装 nodejs(npm)。然后执行:

    npm install -g create-react-app
    

    如果上述命令执行失败可以运行以下命令:

    npm install -g create-react-app --registry=https://registry.npm.taobao.org
    

    然后建立一个 react 并运行:

    create-react-app myApp
    cd myApp
    npm start
    

    这样你就简单的完成了一个 react app 建立,其目录结构如下( 图中不包括 node_modules 目录,下同 ):

    Hello World

    我们删除一些不必要的东西,然后修改目录结构如下(不能删 node_modules 目录,如果删了就在项目目录下运行 npm i 就好了):

    其中 components 是个目录。

    修改 index.js 如下:

    import React from 'react';
    import ReactDOM from 'react-dom';
    
    ReactDOM.render(
      <h1> hello world! </h1>,
      document.getElementById('root')
    );
    

    然后命令行运行:

    npm start
    

    你就可以看到熟悉的 'hello world' 了

    JSX

    JSX 是 react 中允许 js 和 html 混写的语法格式,需要依赖 babel 编译。这里我就只研究它的语法:

    const element = <h1>Hello, world!</h1>;
    

    可以通过花括号在其中插入表达式:

    function formatName(user){
      return user.firstName + ' ' + user.lastName;
    }
    
    const user = {
      firstName: 'Harper',
      lastName: 'Perez'
    };
    
    const element = (
      <h1>
        Hello, {formatName(user)}!
      </h1>
    );
    
    ReactDOM.render(
      element,
      document.getElementById('root')
    );
    

    可以将 HTML 语句写为多行以增加可读性,用小括号括起来可以防止自动插入分号导致的错误。

    JSX 也是个表达式,所以可以用在 for 和 if 中:

      function getGreeting(user){
        if (user){
          return <h1>Hello, {formatName(user)}!</h1>;
        }
        return <h1>Hello, Stranger.</h1>;
      }
    

    我们可以正常使用引号给 HTML 标签添加属性,也可以使用 js 表达式

    const element = <div tabIndex="0"></div>;
    
    const element = <img src={user.avatarUrl} />;   //注意空标签以 /> 结尾,像 XML 一样
    

    注意 html 属性名请使用小驼峰(camelCase)写法

    React 会在渲染之前 escape 所有在 JSX 嵌入的值,可以有效的防止 XSS 攻击。

    babel 会编译 JSX 成 React.createElement() 的参数调用:

    const element = (
      <h1 className="greeting">
        Hello, world!
      </h1>
    );
    // 编译为以下形式
    const element = React.createElement(
      'h1',
      {className: 'greeting'},
      'Hello, world!'
    );
    

    而 React.createElement() 会生成这样一个对象(React 元素):

    const element = {
      type: 'h1',
      props: {
        className: 'greeting',
        children: 'Hello, world'
      }
    };
    

    元素渲染

    ./public/index.html 中有一个 id 为 root 的 div。我们将这个 div 作为 react 渲染的容器。

    回看 hello world 程序,通过 ReactDOM.render() 方法很轻松的把内容渲染到了目标容器上:

    ReactDOM.render(
      <h1> hello world! </h1>,
      document.getElementById('root')
    );
    

    当然也可以这样写:

    let content = <h1> hello world! </h1>;
    ReactDOM.render(
      content,
      document.getElementById('root')
    );
    

    下面我们写一个复杂的,这是个实时更新的时钟,通过 setInerval 每隔 1s 调用 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);
    

    组件

    React 给我们提供了更好的管理我的代码——组件。这里我们还是首先我们先了解一下自定义标签:

    const element = <Welcome name="Sara" />;
    

    对这个标签的理解也不难,它实际上调用了 Welcome 函数,并且将所有的属性(这里只有name)打包为一个对象传给 Welcome 函数。所以下面这个代码输出 ”Hello Sara"

    function Welcome(props){
      return <h1>Hello, {props.name}</h1>;
    }
    
    const element = <Welcome name="Sara" />;
    ReactDOM.render(
      element,
      document.getElementById('root')
    );
    

    组件帮助我事先一些重复的工作,比如这样:

    function Welcome(props){
      return <h1>Hello, {props.name}</h1>;
    }
    
    function App(){
      return (
        <div>
          <Welcome name="Sara" />
          <Welcome name="Cahal" />
          <Welcome name="Edite" />
        </div>
      );
    }
    
    ReactDOM.render(
      <App />,
      document.getElementById('root')
    );
    

    我们可以通过传递参数得到同一个组件构建的不同模块。

    这里我们需要补充一个重要的概念:纯函数!!!

    如果一个函数执行过程中不改变其参数,也不改变其外部作用于参数,当相同的输入总能得到相同的值时,我们称之这样的函数为纯函数。React 要求所有组件函数都必须是纯函数。

    其实之前的一段代码中 Tick, Welcome 函数就可以看做是一个组件,同时 React 建议组件名的首字母大写。但是更多情况下我们会用到 es6 的语法构建组件。以之前时钟代码为例,转换过程分为五个步:

    1. 新建一个类,类名同组件函数名Clock,并继承自 React.Component;
    2. 给该类添加一个方法 render(/无参数/);
    3. 将 Clock 的函数体作为该函数的函数体;
    4. 将 render 方法中的 props 换为 this.props;
    5. 删除原有的 Clock 函数

    结果如下:

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

    但这样计时的功能就不能用了,我们继续往下看……

    State 和 Lifecycle

    解决上面这个问题,就需要用到 State 和 Lifecycle 的知识了

    我们给 Clock 类添加一个构造函数,并且删除 Clock 标签中的参数:

    class Clock extends React.Component {
      constructor(props){
        super(props);
        this.state = {date: new Date()};    //state 用来记录状态
      }
    
      render(){
        return (
          <div>
            <h1>Hello, world!</h1>
            <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
          </div>
        );
      }
    }
    
    ReactDOM.render(
      <Clock />,               //删除参数
      document.getElementById('root')
    );
    

    为了控制计时的生命周期,我们需要引入 2 个方法 componentDidMount() 和 componentWillUnmount(),前者在渲染(render方法)完成时立即执行,后者在该 render 的内容即将被移除前执行。

    很明显,前者适合注册计时器,后者可以用来清除计时器(防止内存泄露)

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

    下一步我们重写 tick 函数,此时的 tick 函数只需要修改 this.state 就行了。注意 React 要求不能直接修改该属性,而是使用 setState() 方法,所以 tick 函数如下:

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

    这里需要注意的是,当 state 中有很多属性的时候,比如:

    this.state = {name:"Lily", age: 12};
    

    执行 setState 方法修改其中的内容时并不会影响未修改的属性:

    this.setState({name: "Bob"});   //此后 this.state 为 {name:"Bob", age: 12};
    

    此外 setState 可能是异步的,所以不要在更新状态时依赖前值:

    // 这是个反例
    this.setState({
      counter: this.state.counter + this.props.increment,
    });
    

    为例解决这个问题,你可以传入函数参数:
    // Correct
    this.setState((prevState, props) => ({ //这里 prevState 更新前的 state 对象,props 为新值构成的对象
    counter: prevState.counter + props.increment
    }));

    此时,完整的代码为:

    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>It is {this.state.date.toLocaleTimeString()}.</h2>
          </div>
        );
      }
    }
    
    ReactDOM.render(
      <Clock />,
      document.getElementById('root')
    );
    

    事件

    React 事件注册和原生 DOM 事件类似的,这里需要理解一些不同点即可:

    • 事件名使用小驼峰写法,而不是全小写,例如:onclick 写作 onClick
    • 注册事件使用花括号表达式替代原有函数写法
    <button onClick={activateLasers}>
      Click Here
    </button>
    
    • 无法通过事件函数 return false 的方式阻止默认事件,必须显式的调用 preventDefault(),并且在使用时不用纠结浏览器兼容问题,React 已经帮你处理好了
    • React 建议通常不需要通过 addEventListener 添加事件,只需要像上方代码那样在 render 时绑定事件即可
    • 在 es6 语法的组件中注册事件只需要将事件函数定义为该类的一个方法,然后在 render 时绑定即可:
    render(){
        return (
          <button onClick={this.handleClick}>
            Click Here...
          </button>
        );
      }
    
    • 在 class 中,除了箭头函数定义的方法中 this 符合预期,其余方法中的 this 都是 undefined,应该手动绑定。因此以下三个按钮中 click2 会报错。
    class Button extends React.Component {
      constructor(){
        super();
        this.name = "Bob";
        this.click3 = this.click2.bind(this);
        this.click1 = () => {
          console.log(`hello ${this.name}`);
        }
      }
      click2(){
        console.log(`hello ${this.name}`);
      }
    
      render(){
        return (
          <raw>
            <button onClick={this.click1}>Click1</button>
            <button onClick={this.click2}>Click2</button>
            <button onClick={this.click3}>Click3</button>
            <button onClick={(e) => this.click2(e)}>Click3</button>
          </raw>
        );
      }
    }
    
    • 以上几种方法,React 推荐使用 click3 的实现方法,重写如下:
    class Button extends React.Component {
      constructor(){
        super();
        this.name = "Bob";
        this.click = this.click.bind(this);
      }
      click(){
        console.log(`hello ${this.name}`);
      }
    
      render(){
        return (
          <raw>
            <button onClick={this.click}>Click me</button>
          </raw>
        );
      }
    }
    
    • 传递参数给事件的时候,第一个参数为 id, 第二个参数为 event。实际调用可以去以下两种方式:
    <button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
    <button onClick={this.deleteRow.bind(this, id, e)}>Delete Row</button>
    
    • 以上两种方法等效,后一个方法的参数 e 可以省略。

    条件渲染

    根据不同的条件(通常指state)渲染不同的内容, 比如下面段代码可以根据 isLoggenIn 渲染不同的问候语:

    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) {              // 根据 isLoggenIn 渲染不同的问候语
        return <UserGreeting />;
      }
      return <GuestGreeting />;
    }
    
    ReactDOM.render(
      // 你可以尝试设置 isLoggedIn={true}:
      <Greeting isLoggedIn={false} />,
      document.getElementById('root')
    );
    

    下面用 class 实现一个复杂一点的,带有登录/注销按钮的:

    function LoginButton(props) {
      return (
        <button onClick={props.onClick}>
          登录
        </button>
      );
    }
    
    function LogoutButton(props) {
      return (
        <button onClick={props.onClick}>
          注销
        </button>
      );
    }
    class LoginControl extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          isLoggedIn: false
        };
    
        // 修正 this 绑定
        this.handleLoginClick = this.handleLoginClick.bind(this);
        this.handleLogoutClick = this.handleLogoutClick.bind(this);
      }
    
      handleLoginClick() {
        this.setState({isLoggedIn: true});
      }
    
      handleLogoutClick() {
        this.setState({isLoggedIn: false});
      }
    
      render() {
        const { isLoggedIn } = this.state;
    
        let button = null;
        if (isLoggedIn) {
          button = <LogoutButton onClick={this.handleLogoutClick} />;
        } else {
          button = <LoginButton onClick={this.handleLoginClick} />;
        }
    
        return (
          <div>
            {/* Greeting 取自上一个示例 (注意这里的注释写法)*/}
            <Greeting isLoggedIn={isLoggedIn} />
            {button}
          </div>
        );
      }
    }
    
    ReactDOM.render(
      <LoginControl />,
      document.getElementById('root')
    );
    

    当然,对于这样一个简单的示例,使用 if 可能你会觉的太复杂了,我们也可以使用 && ?: 这些运算符来代替 if 语句,就像写 javascript 代码一样。我们极力的化简一下上面的代码:

    class LoginControl extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          isLoggedIn: false
        };
      }
    
      render() {
        const { isLoggedIn } = this.state;
        const button = isLoggedIn ?
              <button onClick={() => { this.setState({isLoggedIn: false}); }}>注销</button>
              : <button onClick={() => { this.setState({isLoggedIn: true}); }}>登录</button>;
    
        return (
          <div>
            <h1>
              {
                isLoggedIn ? 'Welcome back!' : 'Please sign up.'
              }
            </h1>
            {button}
          </div>
        );
      }
    }
    
    ReactDOM.render(
      <LoginControl />,
      document.getElementById('root')
    );
    

    当然,如果你需要在某个条件下不进行渲染,那么直接输出 null 即可,比如下面这个组件,在 props.warnfalse 时不渲染任何内容:

    function WarningBanner(props) {
      if (!props.warn) {
        return null;
      }
      return (
        <div className="warning">
          Warning!
        </div>
      );
    }
    

    需要注意的是,即便你输出了 null, react 也会再渲染一次。同理,componentWillUpdatecomponentDidUpdate 也会被调用。

    列表

    在 React 中我们可以使用 map() 方法渲染列表,比如如下这个例子,将一组数据映射(map)为一组 dom:

    const data = [1, 2, 3, 4, 5];
    const listItems = data.map((item) =>
      <li key={number.toString()}>{item}</li>
    );
    ReactDOM.render(
      <ul>{listItems}</ul>,
      document.getElementById('root')
    );
    

    我们注意到这里我们给 li (即列表的每个元素)标签加了一个 key 属性,这个 key 用来帮助 React 判断哪个元素发生了改变、添加或移除。关于这个 key 我们需要明白以下几点:

    1. 最好保证 key 是一个字符串,并且在该列表中唯一,如果你的数据中实在没有唯一的 key 可以选择,那么就使用数组的索引(index)吧(不推荐这样)
    2. 值得注意的是,如果你不给每个元素指定一个 key, react 会默认使用索引(index)作为 key
    3. key 的值只是给 React 起到类似暗示的作用,不会真正的传递给 dom, 所以如果你需要使用 key 的值,应使用一个其它变量传递该值。

    当然,上面代码我们也可以写成 inline 的形式:

    const data = [1, 2, 3, 4, 5];
    ReactDOM.render(
      <ul>
        {
          data.map((item) =>
            <li key={number.toString()}>{item}</li>
          );
        }
      </ul>,
      document.getElementById('root')
    );
    

    表单

    表单的处理会和原生的 html 有一些区别,因为 React 可以很好的帮助你使用 js 控制你的表单,这里我们需要引入一个新的概念:受控组件。

    受控组件说白了就是其值受 react 控制的组件。其中,表单的元素通常都会具有其自己的 state,该值会随着用户的输入改变。比如下面这个例子,会在用户提交时输出用户的名字:

    class NameForm extends React.Component {
      constructor(props) {
        super(props);
        this.state = {value: ''};
    
        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
      }
    
      handleChange(event) {
        this.setState({value: event.target.value});
      }
    
      handleSubmit(event) {
        alert('A name was submitted: ' + this.state.value);
        event.preventDefault();
      }
    
      render() {
        return (
          <form onSubmit={this.handleSubmit}>
            <label>
              Name:
              <input type="text" value={this.state.value} onChange={this.handleChange} />
            </label>
            <input type="submit" value="Submit" />
          </form>
        );
      }
    }
    

    不难发现,这里使用了,onchange 事件不断的将用户的输入绑定到 this.state.value 上,然后通过和用户输入同步的重绘实现数据的显示。这样可以很好的控制用户输入,比如同步的将用户输入转化为大写:

    handleChange(event) {
      this.setState({value: event.target.value.toUpperCase()});
    }
    

    理解了上面的内容我们可以知道,单纯给一个 input 赋值一个值用户是不能修改的,比如下面这行代码:

    ReactDOM.render(<input value="hi" />, mountNode);
    

    但如果你不小心他的值设为 null 或 undefined(等同于没有 value 属性),这个 input 就可以被更改了:

    ReactDOM.render(<input value="hi" />, mountNode);
    setTimeout(function() {
      ReactDOM.render(<input value={null} />, mountNode);
    }, 1000);
    

    在 React 中 textarea 也是通过 value 属性实现其内容变化的,而非其子节点:

    class EssayForm extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          value: 'Please write an essay about your favorite DOM element.'
        };
    
        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
      }
    
      handleChange(event) {
        this.setState({value: event.target.value});
      }
    
      handleSubmit(event) {
        alert('An essay was submitted: ' + this.state.value);
        event.preventDefault();
      }
    
      render() {
        return (
          <form onSubmit={this.handleSubmit}>
            <label>
              Essay:
              <textarea value={this.state.value} onChange={this.handleChange} />
            </label>
            <input type="submit" value="Submit" />
          </form>
        );
      }
    }
    

    在 React 中,对于 select 也会显得很方便,你不需要在 option 中通过 selected 改变其值了,而是在 select 标签上通过 value 属性实现:

    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>
              Pick your favorite La Croix flavor:
              <select value={this.state.value} onChange={this.handleChange}>
                <option value="grapefruit">Grapefruit</option>
                <option value="lime">Lime</option>
                <option value="coconut">Coconut</option>
                <option value="mango">Mango</option>
              </select>
            </label>
            <input type="submit" value="Submit" />
          </form>
        );
      }
    }
    

    上面代码默认选中 Coconut。 这里值得注意的是,对于多选框,你可以传入一个数组作为值:

    <select multiple={true} value={['B', 'C']}>
    

    当你控制很多个表单组件的时候要是为每个组件写一个 handler 方法作为 onChange 事件那就太麻烦了。所以 React 可以通过表单元素的 name 配合 event.target.name 来控制表单:

    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;
        const value = target.type === 'checkbox' ? target.checked : target.value;
        const { name } = target;
    
        this.setState({
          [name]: value
        });
      }
    
      render() {
        return (
          <form>
            <label>
              Is going:
              <input
                name="isGoing"
                type="checkbox"
                checked={this.state.isGoing}
                onChange={this.handleInputChange} />
            </label>
            <br />
            <label>
              Number of guests:
              <input
                name="numberOfGuests"
                type="number"s
                value={this.state.numberOfGuests}
                onChange={this.handleInputChange} />
            </label>
          </form>
        );
      }
    }
    

    state 提升

    这个部分,想说的不是个语法问题,而是代码结构问题。我们重点理解一个例子:计算温度的功能。

    我们实现2个输入框(摄氏温度和华氏温度)的同步数据显示,和对数据的简单操作(判断是否达到标况下水的沸点100摄氏度)

    我们先做点准备工作,比如温度转换函数:

    function toCelsius(fahrenheit) {
      return (fahrenheit - 32) * 5 / 9;
    }
    
    function toFahrenheit(celsius) {
      return (celsius * 9 / 5) + 32;
    }
    

    别忘了,一个好的程序员要能够很好的控制数据输入,所以我们再写一个函数用来处理温度,参数是温度和温度转换函数:

    function tryConvert(temperature, convert) {
      const input = parseFloat(temperature);
      if (Number.isNaN(input) || typeof convert !== 'function') {
        return '';
      }
      const output = convert(input);
      const rounded = Math.round(output * 1000) / 1000;
      return String(rounded);
    }
    

    我们先简单实现这个功能:

    function BoilingVerdict(props) {
      if (props.celsius >= 100) {
        return <p>The water would boil.</p>;
      }
      return <p>The water would not boil.</p>;
    }
    

    然后我们写一个组件用来让用户输入温度

    class Calculator extends React.Component {
      constructor(props) {
        super(props);
        this.handleChange = this.handleChange.bind(this);
        this.state = {temperature: ''};
      }
    
      handleChange(e) {
        this.setState({temperature: e.target.value});
      }
    
      render() {
        const { temperature } = this.state;
        return (
          <fieldset>
            <legend>Enter temperature in Celsius:</legend>
            <input
              value={temperature}
              onChange={this.handleChange} />
    
            <BoilingVerdict
              celsius={parseFloat(temperature)} />
          </fieldset>
        );
      }
    }
    

    此时我们可以输入摄氏温度了,再添加一个数据华氏温度的地方。这里我们从上面的 Calculator 中提出来输入组件:

    const scaleNames = {
      c: 'Celsius',
      f: 'Fahrenheit'
    };
    
    class TemperatureInput extends React.Component {
      constructor(props) {
        super(props);
        this.handleChange = this.handleChange.bind(this);
        this.state = {temperature: ''};
      }
    
      handleChange(e) {
        this.setState({temperature: e.target.value});
      }
    
      render() {
        const { temperature } = this.state;
        const { scale } = this.props;
        return (
          <fieldset>
            <legend>Enter temperature in {scaleNames[scale]}:</legend>
            <input value={temperature}
                   onChange={this.handleChange} />
          </fieldset>
        );
      }
    }
    

    这样 Calculator 就简单了:

    class Calculator extends React.Component {
      render() {
        return (
          <div>
            <TemperatureInput scale="c" />
            <TemperatureInput scale="f" />
          </div>
        );
      }
    }
    

    这样2个输入框就有了,但是它们还不能同步变化。而且 Calculator 组件不知道水温是多少了,没法判断温度了。这是我们应该吧温度状态放在他们最近的公共祖先元素上,这里就是 Calculator 组件啦。

    很明显,首先要改的就是 TemperatureInput, 它不需要 state 了,我们应该从参数获取温度了:

    class TemperatureInput extends React.Component {
      constructor(props) {
        super(props);
        this.handleChange = this.handleChange.bind(this);
      }
    
      handleChange(e) {
        this.props.onTemperatureChange(e.target.value);
      }
    
      render() {
        const { temperature, scale } = this.props;
        return (
          <fieldset>
            <legend>Enter temperature in {scaleNames[scale]}:</legend>
            <input value={temperature}
                   onChange={this.handleChange} />
          </fieldset>
        );
      }
    }
    

    之后我们修改 Calculator 的 state, 将 temperature 和 scale 放入其中, 并添加状态转换函数:

    class Calculator extends React.Component {
      constructor(props) {
        super(props);
        this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
        this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
        this.state = {temperature: '', scale: 'c'};
      }
    
      handleCelsiusChange(temperature) {
        this.setState({scale: 'c', temperature});
      }
    
      handleFahrenheitChange(temperature) {
        this.setState({scale: 'f', temperature});
      }
    
      render() {
        const { temperature, scale } = this.state;
        const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
        const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;
    
        return (
          <div>
            <TemperatureInput
              scale="c"
              temperature={celsius}
              onTemperatureChange={this.handleCelsiusChange} />
    
            <TemperatureInput
              scale="f"
              temperature={fahrenheit}
              onTemperatureChange={this.handleFahrenheitChange} />
    
            <BoilingVerdict
              celsius={parseFloat(celsius)} />
    
          </div>
        );
      }
    }
    

    到此所有的工作就完成了。我们总结一下,输入数据时都发生了什么

    • 当用户输入时,react 调用了已经申明的函数作为 onChange 事件。在这个例子中,就是 TemperatureInput 中的 handleChange 方法。
    • TemperatureInput 中的 handleChange 方法调用了 this.props.onTemperatureChange(), 并传入了最新的值。而这个方法由父组件 Calculator 提供。
    • 在前一次渲染中,Calculator 已经将 Celsius TemperatureInput 的 onTemperatureChange 设置为 handleCelsiusChange,并将 Fahrenheit TemperatureInput 的 onTemperatureChange 设置为 handleFahrenheitChange。所以这两个计算方法的调用取决于我们编辑哪一个 input。
    • 在这两个方法中,Calculator 组件通过调用 this.setState() 方法让 react 以最新的输入和当前 scale 重绘该组件。
    • React 调用 Calculator 组件的 render 方法渲染页面,两个 input 中的值会基于当前值和 scale 重新计算, 温度转换函数就是在这里被调用的。
    • React 通过 props 使用 Calculator 新传入的数据,分别调用每个 TemperatureInput 模块中的 render 方法渲染 input 组件。
    • React DOM 更新 DOM 树匹配新的数据,我们编辑的 input 得到我们刚刚输入的值,而另一个 input 得到转换后的值。

    我们看看官方给出的效果:

    [图片上传失败...(image-a04057-1516513457480)]

    组合与继承

    React 建议用组件组合的方式代替组件继承。所以我们需要学习如何用组合代替继承。

    很多组件在事先是不知道自己的孩子(内部的元素)的。比如对话框这样的盒子型元素。我们需要使用 children 属性来解决这个问题

    function FancyBorder(props) {
      return (
        <div className={'FancyBorder FancyBorder-' + props.color}>
          {props.children}
        </div>
      );
    }
    

    props.children 表示通过其他组件调用 FancyBorder 时的全部孩子元素,对应下面例子,children 就是 h1 和 p 的 react 对象数组

    function WelcomeDialog() {
      return (
        <FancyBorder color="blue">
          <h1 className="Dialog-title">
            Welcome
          </h1>
          <p className="Dialog-message">
            Thank you for visiting our spacecraft!
          </p>
        </FancyBorder>
      );
    }
    

    但是当组件缺乏共同点的时候,我们需要在组件中开很多孔,就像下面这个例子,这些孔可以很好的帮我们组合使用很多组件,而且 react 并不限制我我们传递参数的类型

    function SplitPane(props) {
      return (
        <div className="SplitPane">
          <div className="SplitPane-left">
            {props.left}
          </div>
          <div className="SplitPane-right">
            {props.right}
          </div>
        </div>
      );
    }
    
    function App() {
      return (
        <SplitPane
          left={
            <Contacts />
          }
          right={
            <Chat />
          } />
      );
    }
    

    有时候我们想对组件做具体化分类的时候,逻辑上会很像继承,比如 WelcomeDialog 是 Dialog 的一个具体化分类。但在 React 中我们依然用组合的方式实现这个功能:

    function Dialog(props) {
      return (
        <FancyBorder color="blue">
          <h1 className="Dialog-title">
            {props.title}
          </h1>
          <p className="Dialog-message">
            {props.message}
          </p>
        </FancyBorder>
      );
    }
    
    function WelcomeDialog() {
      return (
        <Dialog
          title="Welcome"
          message="Thank you for visiting our spacecraft!" />
    
      );
    }
    

    当然我们也可以用 class 的定义方式:

    function Dialog(props) {
      return (
        <FancyBorder color="blue">
          <h1 className="Dialog-title">
            {props.title}
          </h1>
          <p className="Dialog-message">
            {props.message}
          </p>
          {props.children}
        </FancyBorder>
      );
    }
    
    class SignUpDialog extends React.Component {
      constructor(props) {
        super(props);
        this.handleChange = this.handleChange.bind(this);
        this.handleSignUp = this.handleSignUp.bind(this);
        this.state = {login: ''};
      }
    
      render() {
        return (
          <Dialog title="Mars Exploration Program"
                  message="How should we refer to you?">
            <input value={this.state.login}
                   onChange={this.handleChange} />
    
            <button onClick={this.handleSignUp}>
              Sign Me Up!
            </button>
          </Dialog>
        );
      }
    
      handleChange(e) {
        this.setState({login: e.target.value});
      }
    
      handleSignUp() {
        alert(`Welcome aboard, ${this.state.login}!`);
      }
    }
    

    相关文章

      网友评论

          本文标题:React 快速入门

          本文链接:https://www.haomeiwen.com/subject/dmwfaxtx.html