1. Hello World
这是一个简单的例子,图中我们引入了三个js,前面两个是react的相关文件,最后一个是将ES6/ES7代码编译成ES5代码的作用。ReactDOM.render(
Hello, world!
,
document.getElementById('root')
);
功能是将第一个参数内部的内容渲染到第二个参数中去。第二个是一个元素,第一个看起来是一个元素标签内容。
相信大家注意到图中的script的类型是text/babel,这是babel.js要求的,它要求可以有两种情况:
var scriptTypes = ['text/jsx', 'text/babel'];
const element = (
Hello, {formatName(user)}!
);
它被称为 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,然后形成下面的对象。看源码只是为了了解当前代码的执行情况,可能有很多不懂的地方,但是多了解一点总是会有一点好处。
{formatName(user)}
上面例子中的调用会去执行相应的代码。
const element = ;
也可以使用大括号来定义以 JavaScript 表达式为值的属性:
const element = ;
const title = response.potentiallyMaliciousInput;
// 直接使用是安全的:
const element = {title}
;
React DOM 在渲染之前默认会 过滤 所有传入的值。它可以确保你的应用不会被注入攻击。所有的内容在渲染之前都被转换成了字符串。这样可以有效地防止 XSS(跨站脚本) 攻击。
const element = Hello, world
;
与浏览器的 DOM 元素不同,React 当中的元素事实上是普通的对象,React DOM 可以确保 浏览器 DOM 的数据内容与 React 元素保持一致。
在此 div 中的所有内容都将由 React DOM 来管理,所以我们将其称之为 “根” DOM 节点。React 元素都是immutable 不可变的。当元素被创建之后,你是无法改变其内容或属性的。一个元素就好像是动画里的一帧,它代表应用界面在某一时间点的样子。
根据我们现阶段了解的有关 React 知识,更新界面的唯一办法是创建一个新的元素,然后将它传入 ReactDOM.render() 方法:
*note: 在实际生产开发中,大多数React应用只会调用一次 ReactDOM.render() 。
//声明组件
function Welcome(props) {
return Hello, {props.name}
;
}
但是只有执行下面的代码才会真正的创建组件,执行这行代码之后会执行下图中的代码
const element = ;
通过图中可以看出此时第一个红框参数中有值了,就是我们定义组件函数的时候定义的props。第二个红框可以看到现在的type不是element标签,而是我们创建的函数。
定义一个组件最简单的方式是使用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}
;
}
}
无论是使用函数或是类来声明一个组件,它决不能修改它自己的props。来看这个sum函数:
function sum(a, b) {
return a + b;
}
类似于上面的这种函数称为“纯函数”,它没有改变它自己的输入值,当传入的值相同时,总是会返回相同的结果。
与之相对的是非纯函数,它会改变它自身的输入值:
function withdraw(account, amount) {
account.total -= amount;
}
React是非常灵活的,但它也有一个严格的规则:所有的React组件必须像纯函数那样使用它们的props。
从之前的时钟例子说起:
现在的代码来说是将时钟显示的部分和每秒重新渲染一次的部分耦合在了一起。因为如果有其他的地方需要使用时钟显示的部分,但是不需要每秒执行一次。那么现在这里时钟显示的部分不能够重用。第一步就是封装时钟:
function Clock(props) {
return (
Hello, world!
It is {props.date.toLocaleTimeString()}.
);
}
function tick() {
ReactDOM.render(
,
document.getElementById('root')
);
}
setInterval(tick, 1000);
目前来说时钟部分已经封装成了Clock组件。但是上面我们的说法有点牵强,因为我们将每秒执行一次划分到当前主逻辑去了,按照正常的思想认为:Clock就是一个可以自动更新时间的组件。理想情况,下面的代码直接完成所有的操作:
ReactDOM.render(
,
document.getElementById('root')
);
现在我们需要思考的是:对于一个组建如何完成当前的工作。在继续进行之前,我们还是先了解一些内容
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:组件的内部状态,定义组件的自己的状态,只能定义在组件内部。
首先从上面的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 (
Hello, world!
It is {this.state.date.toLocaleTimeString()}.
);
}
}
ReactDOM.render(
,
document.getElementById('root')
);
class Clock extends React.Component {
constructor(props) {
super(props);
}
domClick(event) {
console.log(this);
console.log(event);
event.preventDefault(); //组织默认行为
}
render() {
return (
Test react event!
);
}
}
ReactDOM.render(
,
document.getElementById('root')
);
在 React 中另一个不同是你不能使用返回 false 的方式阻止默认行为。你必须明确的使用 preventDefault。
domClick(event) {
console.log(this);
console.log(event);
event.preventDefault(); //阻止默认行为
return "this is the result of fuction domClick";
}
render() {
return (
Test react event!
);
}
然后我们去看一下当前当去创建elemnt的时候传进去的prop是什么?
domClick(event) {
console.log(this);
console.log(event);
//event.preventDefault(); //阻止默认行为
}
render() {
return (
Test react event!
);
}
然后看一下当前的结果
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的内部其实是有自己的生命周期函数,以及自己维护的状态,以及与其他组件交互的props。那么我们的this其实很重要,因为可能在我们的方法里是需要去更新当前组件的state,或者去执行父组件传进来的this.props内部的方法。所以我们需要确定我们的方法内部可以取得到当前组件this。
我们还是从上面的最普通的开始说起:
Test react event!
当前情况下函数执行的时候this为undefined。为什么呢?因为当真正执行domClick的时候是浏览器引擎的callback函数,代码的执行由浏览器事件循环机制执行的。我之前有文章说过相关的内容。
Function.prototype.bind(thisArg, arg1, arg2, …) 是ES5新增的函数扩展方法,bind()返回一个新的函数对象,该函数的this被绑定到thisArg上,并向事件处理器中传入参数。
所以上面的代码改写如下:
render() {
return (
Test react event!
);
}
这里的原因是因为在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 (
Test react event!
);
}
}
箭头函数则会捕获其所在上下文的this值,作为自己的this值,使用箭头函数就不用担心函数内的this不是指向组件内部了。详情访问:https://blog.csdn.net/it_rod/article/details/71036766#t10
这样的话使用就很方便了,只要是你在调用当前的这个函数的地方都可以使用箭头函数去表示。因为不管下面的几种方式使用哪一种,最后作用域的查找都会查找到组件层的this。除非自己方法内部去重新赋值this。
//第一种
render() {
return (
{this.domClick}}>
Test react event!
);
}
//第二种
domClick = (event) => {
console.log(this);
console.log(event);
event.preventDefault(); //阻止默认行为
//return "this is the result of fuction domClick";
}
render() {
return (
Test react event!
);
}
当然你也可以在构造函数中去使用箭头函数,这里就不将例子写出来了。
render() {
return (
Test react event!
);
}
bind方式和箭头函数方式,这两种为什么可以呢?因为这二者执行完之后返回的都是一个函数,而不是一个函数执行之后的返回值(不是函数返回值)。下面直接将代码贴出来看一下好了。
//箭头函数
domClick = (x, y, event) => {
console.log(this);
console.log(event);
}
render() {
return (
this.domClick(1, 2, e)}>
Test react event!
);
}
//bind
domClick = (x, y, event) => {
console.log(this);
console.log(event);
}
render() {
return (
Test react event!
);
}
* bind方式传递参数,event对象会默认放置(隐式传递)到参数的最后一个位置上。
render() {
const isLoggedIn = this.state.isLoggedIn;
let button = null;
if (isLoggedIn) {
button = ;
} else {
button = ;
}
return (
{button}
);
}
然后是由于我们可以使用JSX表达式,所以也可以使用与元素符 && 和 三目元算符。这两种比较简单,就不贴例子了。
class Clock extends React.Component {
constructor(props) {
super(props);
}
render() {
const numbers = [1, 2, 3, 4, 5];
return numbers.map((number) =>
{number}
);
}
}
ReactDOM.render(
,
document.getElementById('root')
);
上面的代码会包下面的错误:
class Clock extends React.Component {
constructor(props) {
super(props);
}
render() {
const numbers = [1, 2, 3, 4, 5];
return numbers.map((number, index) =>
{number}
);
}
}
ReactDOM.render(
,
document.getElementById('root')
);
至于这样写的原因呢?因为map第一个参数是遍历元素,第二个参数是索引。
const todoItems = todos.map((todo) =>
{todo.text}
);
没有话可以选用其它内容,例如索引index。如果列表可以重新排序,我们不建议使用索引来进行排序,因为这会导致渲染变得很慢。
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')
);
当用户提交表单时,HTML的默认行为会使这个表单跳转到一个新页面。在React中亦是如此。但大多数情况下,我们都会构造一个处理提交表单并可访问用户输入表单数据的函数。实现这一点的标准方法是使用一种称为“受控组件”的技术。
在HTML当中,像,
我们通过使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 (
);
}
}
ReactDOM.render(
,
document.getElementById('root')
);
使用 react 经常会遇到几个组件需要共用状态数据的情况。这种情况下,我们最好将这部分共享的状态提升至他们最近的父组件当中进行管理。
创建一个温度计算器来计算水是否会在给定的温度下烧开,用户输入当前温度数。
现在我们有了一个新的需求,在提供摄氏度输入的基础之上,再提供一个华氏温度输入,并且它们能保持同步。
分析一下:
需求是将摄氏温度和华氏温度保持同步,问题在于如何取保持。以当前的代码来看,当用户修改输入,需要将当前温度发送给另一个存储华氏温度的组建,而且还能够接受到华氏温度的最新变化值。这个东西看起来很复杂,是因为两个组件的存在如何进行通信,好像目前了解到的只是没有能够解决的。
那么就只有想一下目前已经存在的知识。好像只有props。但是props的使用适用于父子组件间的。所以我们需要将显示温度相关内容(TemperatureInput)提出来,就是摄氏温度与华氏温度都存在与Calculator。然后TemperatureInput的显示就由Calculator决定。
说到TemperatureInput的显示就由Calculator决定,那么现在Calculator内渲染结构如下:
首先呢,scale参数判断是摄氏温度还是华氏温度。temperature为具体温度值,第三个为一个方法。为什么会有这个方法呢?因为首先在Calculator内部而言,什么时候重新渲染组件(也就是调用render函数)是由Calculator自己决定的。但是呢现在用户的输入实在子组件TemperatureInput中。这就导致子组件你在重新输入,不会让父组件重新渲染,父组件不重新渲染,那么两个组件中的值就不会保持一致。所以当子组件的温度值发生变化的时候,我么还需要通知父组件更新温度值,并且重新渲染。那么这就是第三个参数存在的意义。当子组件更新的时候,需要调用父组件的方法,以达到更新父组件的state,然后达到重新render,重新向子组件传入新的props,保持数据的一致。
改写如下:
function FancyBorder(props) {
return (
{props.children}
);
}
function WelcomeDialog() {
return (
Welcome
Thank you for visiting our spacecraft!
);
}
ReactDOM.render(
,
document.getElementById('root')
);
我们看一下FancyBorder内部的props.children是什么?
可以看到传入的就是
之前我们了解过属性,这里我们也可以将子元素以属性的方式传递:
function FancyBorder(props) {
return (
{props.left}
{props.right}
);
}
function WelcomeDialog() {
return (
Welcome
}
right={
Thank you for visiting our spacecraft!
}/>
);
}
ReactDOM.render(
,
document.getElementById('root')
);
这里我写了两个demo,一个是不使用react-route实现的单页面应用,另外一个是以来react-demo。
react router without react-route
react-route