React 快速入门

1. Hello World

<!DOCTYPE html>
<html>
<head>
    <script src="../js/react.development.js"></script>
    <script src="../js/react-dom.development.js"></script>
    <script src="https://unpkg.com/[email protected]/babel.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
    ReactDOM.render(
        <h1>Hello, world!</h1>,
        document.getElementById('root')
    );
</script>
</body>
</html>
这是一个简单的例子,图中我们引入了三个js,前面两个是react的相关文件,最后一个是将ES6/ES7代码编译成ES5代码的作用。
react相关文件:https://github.com/facebook/react/releases
babel.js: https://unpkg.com/[email protected]/
ReactDOM.render(
    <h1>Hello, world!</h1>,
    document.getElementById('root')
);

功能是将第一个参数内部的内容渲染到第二个参数中去。第二个是一个元素,第一个看起来是一个元素标签内容。
相信大家注意到图中的script的类型是text/babel,这是babel.js要求的,它要求可以有两种情况:

var scriptTypes = ['text/jsx', 'text/babel'];


2. JSX

<!DOCTYPE html>
<html>
<head>
    <script src="../js/react.development.js"></script>
    <script src="../js/react-dom.development.js"></script>
    <script src="https://unpkg.com/[email protected]/babel.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
    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')
    );
</script>
</body>
</html>


JSX 简介

上面例子中使用了下面的代码
const element = (
    <h1>
        Hello, {formatName(user)}!
    </h1>
);
它被称为 JSX, 一种 JavaScript 的语法扩展。 推荐在 React 中使用 JSX 来描述用户界面。JSX 乍看起来可能比较像是模版语言,但事实上它完全是在 JavaScript 内部实现的。 为什么这样说呢?因为当这段代码babel会transform成下面code这样:
"'use strict';

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

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

var element = React.createElement(
    'h1', //element类型
    null, //属性
    'Hello, ',
    formatName(user),
    '!'
);

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

可以看出最后会执行的ReactDOM.createElement,然后形成下面的对象。看源码只是为了了解当前代码的执行情况,可能有很多不懂的地方,但是多了解一点总是会有一点好处。



在 JSX 中使用表达式

在 JSX 当中使用 JavaScript 表达式,在 JSX 当中的表达式要包含在大括号里。
{formatName(user)}
上面例子中的调用会去执行相应的代码。


JSX 属性

你可以使用引号来定义以字符串为值的属性:
const element = <div tabIndex="0"></div>;
也可以使用大括号来定义以 JavaScript 表达式为值的属性:
const element = <img src={user.avatarUrl}></img>;

JSX 防注入攻击

const title = response.potentiallyMaliciousInput;
// 直接使用是安全的:
const element = <h1>{title}</h1>;
React DOM 在渲染之前默认会 过滤 所有传入的值。它可以确保你的应用不会被注入攻击。所有的内容在渲染之前都被转换成了字符串。这样可以有效地防止 XSS(跨站脚本) 攻击。

JSX 代表 Objects

Babel 转译器会把JSX转换成一个名为React.createElement() 的方法调用(也就是上面的图中的createElementWithValidation方法)。
这样的对象被称为 “React 元素”。它代表所有你在屏幕上看到的东西。React 通过读取这些对象来构建 DOM 并保持数据内容一致。


3. 元素渲染

元素是构成 React 应用的最小单位。
元素用来描述你在屏幕上看到的内容:
const element = <h1>Hello, world</h1>;
与浏览器的 DOM 元素不同,React 当中的元素事实上是普通的对象,React DOM 可以确保 浏览器 DOM 的数据内容与 React 元素保持一致。

将元素渲染到 DOM 中 

首先我们在一个 HTML 页面中添加一个 id="root" 的 <div>:
<div id="root"></div>
在此 div 中的所有内容都将由 React DOM 来管理,所以我们将其称之为 “根” DOM 节点。
我们用React 开发应用时一般只会定义一个根节点。渲染的代码会使用ReactDOM.render进行渲染。

更新元素渲染

React 元素都是immutable 不可变的。当元素被创建之后,你是无法改变其内容或属性的。一个元素就好像是动画里的一帧,它代表应用界面在某一时间点的样子。

根据我们现阶段了解的有关 React 知识,更新界面的唯一办法是创建一个新的元素,然后将它传入 ReactDOM.render() 方法:

<!DOCTYPE html>
<html>
<head>
    <script src="../js/react.development.js"></script>
    <script src="../js/react-dom.development.js"></script>
    <script src="https://unpkg.com/[email protected]/babel.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
    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);
</script>
</body>
</html>

*note: 在实际生产开发中,大多数React应用只会调用一次 ReactDOM.render() 。


React 只会更新必要的部分

React DOM 首先会比较元素内容先后的不同,而在渲染过程中只会更新改变了的部分。即便我们每秒都创建了一个描述整个UI树的新元素,React DOM 也只会更新渲染文本节点中发生变化的内容。



4. 组件 & Props

<!DOCTYPE html>
<html>
<head>
    <script src="../js/react.development.js"></script>
    <script src="../js/react-dom.development.js"></script>
    <script src="https://unpkg.com/[email protected]/babel.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
    function Welcome(props) {
        return <h1>Hello, {props.name}</h1>;
    }

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

但是只有执行下面的代码才会真正的创建组件,执行这行代码之后会执行下图中的代码

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

通过图中可以看出此时第一个红框参数中有值了,就是我们定义组件函数的时候定义的props。第二个红框可以看到现在的type不是element标签,而是我们创建的函数。



函数定义/类定义组件

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

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

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

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

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

Props的只读性

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

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

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

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

function withdraw(account, amount) {
  account.total -= amount;
}
React是非常灵活的,但它也有一个严格的规则:所有的React组件必须像纯函数那样使用它们的props。


5. State & 生命周期

从之前的时钟例子说起:

<!DOCTYPE html>
<html>
<head>
    <script src="../js/react.development.js"></script>
    <script src="../js/react-dom.development.js"></script>
    <script src="https://unpkg.com/[email protected]/babel.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
    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);
</script>
</body>
</html>

现在的代码来说是将时钟显示的部分和每秒重新渲染一次的部分耦合在了一起。因为如果有其他的地方需要使用时钟显示的部分,但是不需要每秒执行一次。那么现在这里时钟显示的部分不能够重用。第一步就是封装时钟:

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组件。但是上面我们的说法有点牵强,因为我们将每秒执行一次划分到当前主逻辑去了,按照正常的思想认为:Clock就是一个可以自动更新时间的组件。

理想情况,下面的代码直接完成所有的操作:

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);
现在我们需要思考的是:对于一个组建如何完成当前的工作。
(1) 设置一个定时器,取每一秒执行一次,然后将页面的时钟显示更新一次。这部分内容应该放在组建初始化的时候。
(2) 当组建卸载的时候需要将当前定时器清除掉,因为这个定时器会造成内存泄露,因为当前加载的不是这个组建,所以内部的资源都要清除掉。这部分内容要放在组建卸载的时候。
(3) 组建生存周期内,需要代码告知当前组建内容需要重新渲染。

在继续进行之前,我们还是先了解一些内容

生命周期钩子简介

componentWillMount 在渲染前调用,在客户端也在服务端。

componentDidMount : 在第一次渲染后调用,只在客户端。之后组件已经生成了对应的DOM结构,可以通过this.getDOMNode()来进行访问。 如果你想和其他JavaScript框架一起使用,可以在这个方法中调用setTimeout, setInterval或者发送AJAX请求等操作(防止异部操作阻塞UI)。

componentWillReceiveProps 在组件接收到一个新的 prop (更新后)时被调用。这个方法在初始化render时不会被调用

shouldComponentUpdate 返回一个布尔值。在组件接收到新的props或者state时被调用。在初始化时或者使用forceUpdate时不被调用。 可以在你确认不需要更新组件时使用。

componentWillUpdate在组件接收到新的props或者state但还没有render时被调用。在初始化时不会被调用。

componentDidUpdate 在组件完成更新后立即调用。在初始化时不会被调用。

componentWillUnmount在组件从 DOM 中移除的时候立刻被调用。

state和prop的区别

props:组件间的状态传递,从父组件到子组件的数据传递。

state:组件的内部状态,定义组件的自己的状态,只能定义在组件内部。

state的相关知识

首先从上面的state和prop的区别来看,state是组件内部维护的。然后开头的本小节开头的时候举了时钟例子,而且prop是不可变的。在没有state的情况下我们只有通过定时器函数去执行更新。现在我们知道state的概念了,那么我们可以使用当前状态去更新渲染。现在我目前的理解是state是一个当前组件内部维护的对象,当我们通过代码更新state的值,组件会自动检测到state的变化,进而去更新当前组件渲染(这里的理解为state使我们已经表明那些数据变化需要去更新组件)。

初始化 

constructor(props) {
    super(props);
    this.state = {date: new Date()};
}

更新状态方式(必须使用setState)

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

时钟组件的改写

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

JSX语法语法绑定事件

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

    domClick(event) {
    	console.log(this);
        console.log(event);
        event.preventDefault(); //组织默认行为
    }

    render() {
        return (
            <div onClick={this.domClick}>
                Test react event!
            </div>
        );
    }
}

ReactDOM.render(
    <Clock />,
    document.getElementById('root')
);
在 React 中另一个不同是你不能使用返回 false 的方式阻止默认行为。你必须明确的使用 preventDefault。

react 事件绑定函数带括号和不带括号的区别

上面我们理解到当前react的事件绑定是需要JSX语法。现在我将上面的代码修改一下,然后去进行我们的测试:
domClick(event) {
    console.log(this);
    console.log(event);
    event.preventDefault(); //阻止默认行为

    return "this is the result of fuction domClick";
}

render() {
    return (
        <div onClick={this.domClick}>
            Test react event!
        </div>
    );
}

然后我们去看一下当前当去创建elemnt的时候传进去的prop是什么?


这个时候我们可以看到当前穿进去的是一个函数,然后我们在将代码改成
domClick(event) {
    console.log(this);
    console.log(event);
    //event.preventDefault(); //阻止默认行为
}

render() {
    return (
        <div onClick={this.domClick()}>
            Test react event!
        </div>
    );
}

然后看一下当前的结果


从上面的结果可以看出当我们不加上括号的时候是正常的情况,因为此时dom绑定的是一个函数,但是加上括号,函数会先执行,然后将执行的结果进行dom绑定。正常会报错的,错误如下:
react-dom.development.js:526 Warning: Expected `onClick` listener to be a function, instead got a value of `string` type.
    in div (created by Clock)
    in Clock
但是呢,由于普通情况下用于绑定事件的函数都不会有返回值,所以不会报错,只是设置的事件绑定不会执行。这也是上面修改的code将注视了一行代码,因为没有event对象,因为当前就是函数执行表达式。

react this

从之前的内容我们了解到react的内部其实是有自己的生命周期函数,以及自己维护的状态,以及与其他组件交互的props。那么我们的this其实很重要,因为可能在我们的方法里是需要去更新当前组件的state,或者去执行父组件传进来的this.props内部的方法。所以我们需要确定我们的方法内部可以取得到当前组件this。

我们还是从上面的最普通的开始说起:

<div onClick={this.domClick}>
    Test react event!
</div>
当前情况下函数执行的时候this为undefined。为什么呢?因为当真正执行domClick的时候是浏览器引擎的callback函数,代码的执行由浏览器事件循环机制执行的。我之前有文章说过相关的内容。

bind()

详情访问:https://blog.csdn.net/it_rod/article/details/71036766#t8

Function.prototype.bind(thisArg, arg1, arg2, …) 是ES5新增的函数扩展方法,bind()返回一个新的函数对象,该函数的this被绑定到thisArg上,并向事件处理器中传入参数。

所以上面的代码改写如下:

render() {
    return (
        <div onClick={this.domClick.bind(this)}>
            Test react event!
        </div>
    );
}


构造函数内绑定

在构造函数 constructor 内绑定this,好处是仅需要绑定一次,避免每次渲染时都要重新绑定,函数在别处复用时也无需再次绑定。

这里的原因是因为在constructor函数中的内容都会执行一次。但是为什么可以这样做呢?

react内部在穿件组件的时候会执行一个方法constructClassInstance,内部是通过var instance = new ctor(props, context)创建组件。而且编译的时候构造函数的执行就是相当于new一个普通的function。至于new 一个普通的function的this是指向当前生成的对象的。详情可以访问:https://blog.csdn.net/it_rod/article/details/71036766#t8

class Clock extends React.Component {
    constructor(props) {
        super(props);
        this.domClick = this.domClick.bind(this)
    }

    domClick(event) {
        console.log(this);
        console.log(event);
        event.preventDefault(); //阻止默认行为

        //return "this is the result of fuction domClick";
    }

    render() {
        return (
            <div onClick={this.domClick}>
                Test react event!
            </div>
        );
    }
}

箭头函数

箭头函数则会捕获其所在上下文的this值,作为自己的this值,使用箭头函数就不用担心函数内的this不是指向组件内部了。详情访问:https://blog.csdn.net/it_rod/article/details/71036766#t10

这样的话使用就很方便了,只要是你在调用当前的这个函数的地方都可以使用箭头函数去表示。因为不管下面的几种方式使用哪一种,最后作用域的查找都会查找到组件层的this。除非自己方法内部去重新赋值this。

//第一种
render() {
    return (
        <div onClick={()=>{this.domClick}}>
            Test react event!
        </div>
    );
}
//第二种
domClick = (event) => {
    console.log(this);
    console.log(event);
    event.preventDefault(); //阻止默认行为


    //return "this is the result of fuction domClick";
}

render() {
    return (
        <div onClick={this.domClick}>
            Test react event!
        </div>
    );
}
当然你也可以在构造函数中去使用箭头函数,这里就不将例子写出来了。

向事件处理程序传递参数

还是先将之前上面的例子拿下来看一下,下面是一种正常可以进行事件调用的代码书写。所以请回顾一下上面我们所说的domClick加括号和不加括号的区别。所以这里当我们加上括号和参数的话必须返回值还是一个函数才行。所以直接加括号不行的。
render() {
    return (
        <div onClick={this.domClick}>
            Test react event!
        </div>
    );
}
bind方式和箭头函数方式,这两种为什么可以呢?因为这二者执行完之后返回的都是一个函数,而不是一个函数执行之后的返回值(不是函数返回值)。下面直接将代码贴出来看一下好了。
//箭头函数
domClick = (x, y, event) => {
    console.log(this);
    console.log(event);
}

render() {
    return (
        <div onClick={(e) => this.domClick(1, 2, e)}>
            Test react event!
        </div>
    );
}

//bind
domClick = (x, y, event) => {
    console.log(this);
    console.log(event);
}

render() {
    return (
        <div onClick={this.domClick.bind(this, 1, 2)}>
            Test react event!
        </div>
    );
}
* bind方式传递参数,event对象会默认放置(隐式传递)到参数的最后一个位置上。

条件渲染

在 React 中,你可以创建不同的组件来封装各种你需要的行为。然后还可以根据应用的状态变化只渲染其中的一部分。
由于虚拟DOM的创建,所以我们可以根据变量来控制return 不同的返回值。
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>
    );
}
然后是由于我们可以使用JSX表达式,所以也可以使用与元素符 && 和 三目元算符。这两种比较简单,就不贴例子了。

阻止组件渲染

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

列表和Keys

首先使用map遍历数组元素然后渲染。
class Clock extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        const numbers = [1, 2, 3, 4, 5];
        return numbers.map((number) =>
            <li>{number}</li>
        );
    }
}

ReactDOM.render(
    <Clock />,
    document.getElementById('root')
);
上面的代码会包下面的错误:

尽管目前我们还不知道这个错误是什么问题,但是通过提示我们可以看出5个li元素具有相同的key,至于这个key值是什么,目前不考虑,因为当前我只需要将key的值给与不同的值。
class Clock extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        const numbers = [1, 2, 3, 4, 5];
        return numbers.map((number, index) =>
            <li key={index}>{number}</li>
        );
    }
}

ReactDOM.render(
    <Clock />,
    document.getElementById('root')
);
至于这样写的原因呢?因为map第一个参数是遍历元素,第二个参数是索引。

Keys

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

首选id作为key

一个元素的key最好是这个元素在列表中拥有的一个独一无二的字符串。通常,我们使用来自数据的id作为元素的key。
const todoItems = todos.map((todo) =>
  <li key={todo.id}>
    {todo.text}
  </li>
);
没有话可以选用其它内容,例如索引index。如果列表可以重新排序,我们不建议使用索引来进行排序,因为这会导致渲染变得很慢。

用keys提取组件

function ListItem(props) {
    return <li>{props.value}</li>;
}

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

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
    <NumberList numbers={numbers} />,
    document.getElementById('root')
);

表单

HTML表单元素与React中的其他DOM元素有所不同,因为表单元素生来就保留一些内部状态。

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

在HTML当中,像<input>,<textarea>, 和 <select>这类表单元素会维持自身状态,并根据用户输入进行更新。但在React中,可变的状态通常保存在组件的状态属性中,并且只能用 setState() 方法进行更新。

我们通过使react变成一种单一数据源的状态来结合二者。React负责渲染表单的组件仍然控制用户后续输入时所发生的变化。相应的,其值由React控制的输入表单元素称为“受控组件”。

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

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

状态提升

使用 react 经常会遇到几个组件需要共用状态数据的情况。这种情况下,我们最好将这部分共享的状态提升至他们最近的父组件当中进行管理。

创建一个温度计算器来计算水是否会在给定的温度下烧开,用户输入当前温度数。

<script type="text/babel">
    function BoilingVerdict(props) {
        if (props.celsius >= 100) {
            return <p>水会烧开</p>;
        }
        return <p>水不会烧开</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.temperature;
            return (
                <fieldset>
                    <legend>输入一个摄氏温度</legend>
                    <input
                        value={temperature}
                        onChange={this.handleChange} />

                    <BoilingVerdict
                        celsius={parseFloat(temperature)} />
                </fieldset>
            );
        }
    }

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

华氏温度新需求

现在我们有了一个新的需求,在提供摄氏度输入的基础之上,再提供一个华氏温度输入,并且它们能保持同步。

分析一下: 

需求是将摄氏温度和华氏温度保持同步,问题在于如何取保持。以当前的代码来看,当用户修改输入,需要将当前温度发送给另一个存储华氏温度的组建,而且还能够接受到华氏温度的最新变化值。这个东西看起来很复杂,是因为两个组件的存在如何进行通信,好像目前了解到的只是没有能够解决的。

那么就只有想一下目前已经存在的知识。好像只有props。但是props的使用适用于父子组件间的。所以我们需要将显示温度相关内容(TemperatureInput)提出来,就是摄氏温度与华氏温度都存在与Calculator。然后TemperatureInput的显示就由Calculator决定。

说到TemperatureInput的显示就由Calculator决定,那么现在Calculator内渲染结构如下:

<TemperatureInput
    scale="c"
    temperature={celsius}
    onTemperatureChange={this.handleCelsiusChange} />

<TemperatureInput
    scale="f"
    temperature={fahrenheit}
    onTemperatureChange={this.handleFahrenheitChange} />

首先呢,scale参数判断是摄氏温度还是华氏温度。temperature为具体温度值,第三个为一个方法。为什么会有这个方法呢?因为首先在Calculator内部而言,什么时候重新渲染组件(也就是调用render函数)是由Calculator自己决定的。但是呢现在用户的输入实在子组件TemperatureInput中。这就导致子组件你在重新输入,不会让父组件重新渲染,父组件不重新渲染,那么两个组件中的值就不会保持一致。所以当子组件的温度值发生变化的时候,我么还需要通知父组件更新温度值,并且重新渲染。那么这就是第三个参数存在的意义。当子组件更新的时候,需要调用父组件的方法,以达到更新父组件的state,然后达到重新render,重新向子组件传入新的props,保持数据的一致。

改写如下:

<!DOCTYPE html>
<html>
<head>
    <script src="../js/react.development.js"></script>
    <script src="../js/react-dom.development.js"></script>
    <script src="https://unpkg.com/[email protected]/babel.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
    function BoilingVerdict(props) {
        if (props.celsius >= 100) {
            return <p>水会烧开</p>;
        }
        return <p>水不会烧开</p>;
    }

    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)) {
            return '';
        }
        const output = convert(input);
        const rounded = Math.round(output * 1000) / 1000;
        return rounded.toString();
    }

    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});
            this.props.onTemperatureChange(e.target.value);
        }

        render() {
            const temperature = this.props.temperature;
            const scale = this.props.scale;
            return (
                <fieldset>
                    <legend>Enter temperature in {scaleNames[scale]}:</legend>
                    <input value={temperature}
                           onChange={this.handleChange} />
                </fieldset>
            );
        }
    }

    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 scale = this.state.scale;
            const temperature = this.state.temperature;
            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>
            );
        }
    }

    ReactDOM.render(
        <Calculator/>,
        document.getElementById('root')
    );
</script>
</body>
</html>

组合

因为前面的例子呢我们使用都是没有子元素的组件,就像上面例子中的<Calculator/>。但是很多情况是会含有子元素的。
function FancyBorder(props) {
    return (
        <div className={'FancyBorder FancyBorder-' + props.color}>
            {props.children}
        </div>
    );
}

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

ReactDOM.render(
    <WelcomeDialog/>,
    document.getElementById('root')
);
我们看一下FancyBorder内部的props.children是什么?

可以看到传入的就是<FancyBorder></<FancyBorder>内部的子元素。


之前我们了解过属性,这里我们也可以将子元素以属性的方式传递:

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

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


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


React Route

这里我写了两个demo,一个是不使用react-route实现的单页面应用,另外一个是以来react-demo。

react router without react-route

react-route

猜你喜欢

转载自blog.csdn.net/it_rod/article/details/80296830