[React] 09 - Tutorial: components

react

jsx变为js的过程:http://babeljs.io/repl/

youtube: https://www.youtube.com/channel/UCA-Jkgr40A9kl5vsIqg-BIg/playlists

raisl365: https://www.rails365.net/movies/you-ren-de-react-shi-pin-jiao-cheng-ji-chu-pian-1-jie-shao

上一节讲了:通过代码的分离来展示client.js文件的进化过程。

关于基础,还需要继续夯实。


先考虑下组件的生命周期

Ref: 基础篇 #14 组件生命周期(完结), 对应代码:https://github.com/hfpp2012/hello-react

Ref: [React] 02 - Intro: why reac and its design pattern - React 组件生命周期

  • Mounting     :已插入真实 DOM 【挂载】

  • Updating      :正在被重新渲染  【更新】

  • Unmounting:已移出真实 DOM 【卸载】

 

生命周期的方法:

  • componentWillMount 【挂载前的操作】在渲染前调用,在客户端也在服务端。

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

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

  • shouldComponentUpdate 【帮助解决一些性能上的问题】返回一个布尔值。在组件接收到新的props或者state时被调用。在初始化时或者使用forceUpdate时不被调用。 可以在你确认不需要更新组件时使用。

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

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

  • componentWillUnmount 【做一些清除的动作】在组件从 DOM 中移除的时候立刻被调用。

官方api文档:https://reactjs.org/docs/react-component.html

Goto: 生命周期流程图

Mounting 第四个componentDidMount常用。

Updating 第四个componentDidUpdate常用。

其他不太常用。 

附加题:React Native 中 component 生命周期

(1) 这里的 app 代表挂载点:

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<title></title>

</head>

<body>

<div id="app"></div>

<script type="text/babel">

ReactDOM.render(

<h1>hello world</h1>,

document.getElementById("app")

)

</script>

<script src="https://cdn.bootcss.com/react/16.2.0/umd/react.development.js"></script>

<script src="https://cdn.bootcss.com/react-dom/16.2.0/umd/react-dom.development.js"></script>

<script src="https://cdn.bootcss.com/babel-standalone/7.0.0-beta.3/babel.js"></script>

</body>

</html>

(2) react 应用最好用的脚手架 create-react-app

  • 创建项目

  1. 先安装nodejs
  2. 再安装create-react-app这条命令
  3. 之后通过该命令生成项目hello-react

 

结果:生成了如下默认的文件,也就是默认空项目。 

 

 

  •  下一步导航提示

  • 开启开发环境的服务器

yarn start ----> 开启3000端口,开始默认的react页面。

 

  • 绑定到静态文件

yarn build ----> 得到如下两条命令提示

yarn global add serve

serve -s build

相继执行两命令,得到如下图: 

默认监听在5000端口,貌似跟上述的端口3000显示的是一样的内容。

以上,便是生成一个新项目到运行的过程。

 

  • 运行现成项目

$ npm start

unsw@unsw-UX303UB$ npm start

> hello-react@0.1.0 start /media/unsw/CloudStorage/Linux-pan/ExtendedTmpSpace/Android-Workplace/android-and-ml/React-Native/demo-react/rails365/hello-react-master

> react-scripts start

Starting the development server...

Compiled successfully!

You can now view hello-react in the browser.

Local: http://localhost:3000/

On Your Network: http://10.248.169.134:3000/

Note that the development build is not optimized.

To create a production build, use yarn build.

View Code

$ npm restart

unsw@unsw-UX303UB$ npm restart

> hello-react@0.1.0 start /media/unsw/CloudStorage/Linux-pan/ExtendedTmpSpace/Android-Workplace/android-and-ml/React-Native/demo-react/rails365/hello-react-master

> react-scripts start

Starting the development server...

Compiled successfully!

You can now view hello-react in the browser.

Local: http://localhost:3000/

On Your Network: http://10.248.169.134:3000/

Note that the development build is not optimized.

To create a production build, use yarn build.

View Code

(3) bootstrap好看的组件

首先

Bootstrap是美国Twitter公司的设计师Mark Otto和Jacob Thornton合作基于HTML、CSS、JavaScript 开发的简洁、直观、强悍的前端开发框架,使得 Web 开发更加快捷。 

Bootstrap提供了优雅的HTML和CSS规范,它即是由动态CSS语言Less写成,就是个“界面工具集”。

 

关于bootstrap文件包简单介绍

文件名称:

1. bootstrap.css

2. bootstrap.min.css

3. bootstrap-responsive.css

4. bootstrap-responsive.min.css

5. bootstrap.js

6. bootstrap.min.js

解释:

1. bootstrap.css 是完整的bootstrap样式表,未经压缩过的,可供开发的时候进行调试用

2. bootstrap.min.css 是经过压缩后的bootstrap样式表,内容和bootstrap.css完全一样,但是把中间不必要的空格之类的东西都删掉了,所以文件大小会比bootstrap.css小,可以在部署网站的时候引用,如果引用了这个文件,就没必要引用bootstrap.css了

3. bootstrap-responsive.css 这个是在对bootstrap框架应用了响应式布局之后所需要的CSS样式表,如果你的网站项目不准备做响应式设计,就不需要引用这个CSS。

4. bootstrap-responsive.min.css 和bootstrap.min.css的作用是一样的,是bootstrap-responsive.css的压缩版

5. bootstrap.js 这个是bootstrap的灵魂所在,是bootstrap的所有js指令的集合,你看到bootstrap里面所有的js效果,都是由这个文件控制的,这个文件也是一个未经压缩的版本,供开发的时候进行调试用

6. bootstrap.min.js 是bootstrap.js的压缩版,内容和bootstrap.js一样的,但是文件大小会小很多,在部署网站的时候就可以不引用bootstrap.js,而换成引用这个文件了~~

对于Bootstrap的认识,看来需要将菜鸟教程概览一遍:Bootstrap 教程

 

其次

代码:https://github.com/hfpp2012/hello-react

  • 安装react开发插件:React Developer Tools

查看组件的层次、各个组件的Props、States等信息,演示如下:

 

app.js的内容,也就是标签<app>实际挂在的地方。

render() {

const user = {

name: "Anna",

hobbies: ["Sports", "Reading"]

}

let homeCmp = "";

if (this.state.homeMounted) {

homeCmp = (

<Home

name={"Max"}

initialAge={12}

user={user}

greet={this.onGreet}

changeLink={this.onChangeLinkName.bind(this)}

initialName={this.state.homeLink}

/>

);

}
return (

/**
* 一段普通的html内容
*/

<div className="container">

<div className="row">

<div className="col-xs-1 col-xs-offset-11">

<Header homeLink={this.state.homeLink} />

</div>

</div>

<div className="row">

<div className="col-xs-1 col-xs-offset-11">

<h1>Hello !!</h1>

</div>

</div>

<div className="row">

<div className="col-xs-1 col-xs-offset-11">

{homeCmp}

</div>

</div>

<hr />

<div className="row">

<div className="col-xs-1 col-xs-offset-11">

<button onClick={this.onChangeHomeMounted.bind(this)} className="btn btn-primary">(Un)mount Home Component</button>

</div>

</div>

</div>

);

}

  • 利用BootCDN写组件 - CDN 加速服务

稳定、快速、免费的前端开源项目 CDN 加速服务。

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="utf-8">

<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

<meta name="theme-color" content="#000000">

<!--

manifest.json provides metadata used when your web app is added to the

homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/

-->

<link rel="manifest" href="%PUBLIC_URL%/manifest.json">

<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">

<link href="https://cdn.bootcss.com/bootstrap/4.0.0-beta.2/css/bootstrap.css" rel="stylesheet">  // insert this url.

(4) 多个组件的开发

这里有两个独立的组件,利于项目分工:

 1. Header.js

 2. Home.js

然后在App.js中引用了这两个组件。

  

(5) 输出动态数据

略,没啥可说的。

 

(6) 组件间消息传递

自定义控件,如何获得组件的“参数”和组件内部的“子组件”?

父组件

import React, { Component } from 'react';

import Header from './components/Header';

import Home from './components/Home';

class App extends Component {

render() {

const user = {              // <---- 这是一个对象

name : "Anna",

hobbies: ["Sports", "Reading"]   // <---- 遍历是需要map

}

return (

<div className="container">

<div className="row">

<div className="col-xs-1 col-xs-offset-11">

<Header />

</div>

</div>

<div className="row">

<div className="col-xs-1 col-xs-offset-11">

<h1>Hello !!</h1>

</div>

</div>

<div className="row">

<div className="col-xs-1 col-xs-offset-11">

<Homename={"Max"} age={12} user={user}>  // <---- 向组件传入三个参数,通过prop作为媒婆

<p>I am child</p>   // <---- 这算是Home的Children节点

</Home>

</div>

</div>

</div>

);

}

}

export default App; 

为了防止参数的自动类型转化,可以执行参数强制检查的策略。 

例如,希望是数字,但可能传入子组件后,自动变成了字符串,搞得我们没有办法拿来做算术运算了呢。

子组件

import React, { Component } from 'react';

import PropTypes from 'prop-types';        // <---- 类型转化

export default class Home extends Component {

render() {

return (
<div className="container">

<div className="row">

<div className="col-xs-1 col-xs-offset-11">

<div>your name is {this.props.name}, your age is {this.props.age}</div>

<div>

<h4>hobbies</h4>

<ul>

{this.props.user.hobbies.map((hobby, i) => <li key={i}>{hobby}</li>)}

</ul>

</div>

<div>{this.props.children}</div>  // <---- 此处获得了"I am child"

</div>

</div>

</div>

);

}

}



/**
* 在此定义参数的类型
*/

Home.propTypes = {

name: PropTypes.string,

age: PropTypes.number,

user: PropTypes.object,

children: PropTypes.element.isRequired

};

 

(7) 事件定义、触发

注意:以下方案只是改变了this.age的值,但没有及时的使UI渲染。

export default class Home extends Component {

constructor(props) {

super(props);

this.age = this.props.age;

}

onMakeOlder() {

this.age += 3;

console.log(this);

}

render() {

return (

<div className="container">

<div className="row">

<div className="col-xs-1 col-xs-offset-11">

<div>your name is {this.props.name}, your age is {this.props.age}</div>

<button onClick={() => {this.onMakeOlder()}} className="btn btn-primary">Make me older</button>
{this.onMakeOlder.bind(this)} // --> 这个方案需要写bind,费事儿

</div>

</div>

</div>

);

}  

}

 

(8) state属性及时渲染

因为:  
constructor(props) {

super(props);

this.state = {

age: props.initialAge

}

}

所以:
{this.props.age} 变为 {this.state.age}

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

  onMakeOlder() { 
   this.setState({
     age: this.state.age + 3
   })
}

 

(9) 虚拟DOM

使用的是DIFF Algorithm。

 

(10) 无状态组件

Ref: 基础篇 #10 无状态组件

简单的代码重构,如下。

import React from 'react';


(1)
export class Header extends Component {
render() {

/**
* <好处>
* 不用再声明类
* 不要显式声明this关键字
* 更简洁,占用内存更小,可以写成无副作用的纯函数
*/

(2)

const Header = (props) => {

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

return (

<div className="container">

<div className="row">

<div className="col-xs-1 col-xs-offset-11">

<h1>Header</h1>

</div>

</div>

</div>

);

};

export default Header;

无状态组件

 1. 无需state, 不处理用户的输入,组件所有的数据都是依赖props传入。

 2. 不需要用到生命周期函数。

 

高阶组件HOC

会返回组件的组件,Redux就是一个实现例子,可处理状态。

 

 

(11) 子组件向父组件传值 

import React, { Component } from 'react';

import Header from './components/Header';

import Home from './components/Home';

class App extends Component {
onGreet(age) {

alert(age);

}

render() {

const user = {

name: "Anna",

hobbies: ["Sports", "Reading"]

}

return (

<div className="container">

<div className="row">

<div className="col-xs-1 col-xs-offset-11">

<Header />

</div>

</div>

<div className="row">

<div className="col-xs-1 col-xs-offset-11">

<h1>Hello !!</h1>

</div>

</div>

<div className="row">

<div className="col-xs-1 col-xs-offset-11">

<Homename={"Max"} initialAge={12} user={user} greet={this.onGreet} />  // onGreet作为参数传递给子组件

</div>

</div>

</div>

);

}

}

export default App;

  

实验:子组件调用父组件的“方法”。 

Home.js,其中的Greet button触发了App组件中的函数,是怎么做到的?

  /**
* 1. 点击 子组件的button, 点击后触发 该函数handleGreet
* 2. 该函数其实是个父附件的函数指针
* 如此,子组件通过父组件给他的一个接口从而改变了父组件自身
*/

handleGreet() {

this.props.greet(this.state.age)  // 其实就是父组件中的onGreet

}

  render() {

return (

<div className="container">

<div className="row">

<div className="col-xs-1 col-xs-offset-11">

<div>your name is {this.props.name}, your age is {this.state.age}</div>

<p>Status: {this.state.status}</p>

<button onClick={() => {this.onMakeOlder()}} className="btn btn-primary">Make me older</button>

<hr />

<button onClick={this.handleGreet.bind(this)} className="btn btn-primary">Greet</button>  

</div>

</div>

</div>

);

}

}

Home.propTypes = {

name: PropTypes.string,

age: PropTypes.number,

user: PropTypes.object,

greet: PropTypes.func  // <---- 添加对应的参数检测

};

  • 为何要bind(this)? 

Ref: 为什么React事件处理函数必须使用Function.bind()绑定this?

经典考题

let obj = {

tmp: 'Yes!',

testLog: function(){

console.log(this.tmp);

}

};

obj.testLog();  # 正常显示‘yes!'
-------------------------------

let tmpLog = obj.testLog;

tmpLog(); # 过度了一下,然后testLog内部的this就变成了”全局this",在这里也就是window

思考:毕竟每一个对象都有自己的this,tmpLog虽然被设置成为了obj.testLog,但自己仍然保留了自己的this。

 

背后的原理

React跟原生JavaScript的事件绑定区别有两点,其中第二点就是:

在React(或者说JSX)中,传递的事件参数不是一个字符串,而是一个实实在在的函数:

这样说,React中的事件名(上图中的onClick)就是我所举例子中的中间变量,React在事件发生时调用onClick,由于onClick只是中间变量,所以处理函数中的this指向会丢失;

为了解决这个问题,我们需要在实例化对象的时候,需要在构造函数中绑定this,使得无论事件处理函数如何传递,它的this的指向都是固定的,固定指向我们所实例化的对象。

 

 

 (12) 子组件之间的传值 

子组件 Home 与 header 间的通信,该怎么搞? ---- 当然是通过父组件”搭桥“。

 

父组件App.js

import React, { Component } from 'react';

import Header from './components/Header';

import Home from './components/Home';

class App extends Component {

constructor() {

super();

this.state = {

homeLink: "Home"

}

}

onGreet(age) {

alert(age);

}

onChangeLinkName(newName) {  // (5) 实际干活的地方,也就是改变了state

this.setState({

homeLink: newName

})

}

render() {

const user = {

name: "Anna",

hobbies: ["Sports", "Reading"]

}
return (

<div className="container">

<div className="row">

<div className="col-xs-1 col-xs-offset-11">

<HeaderhomeLink={this.state.homeLink} />    // (6) 在这里,homelink对于Header子组件就是一个参数;参数被另一个子组件改变,之后,state改变,自然会触发这个子组件UI更新

</div>

</div>

<div className="row">

<div className="col-xs-1 col-xs-offset-11">

<h1>Hello !!</h1>

</div>

</div>

<div className="row">

<div className="col-xs-1 col-xs-offset-11">

<Home

name ={"Max"}

initialAge={12}

user ={user}

greet ={this.onGreet}

changeLink={this.onChangeLinkName.bind(this)}  // (4) app 给子组件传递个“函数指针“,改变的state其实是属于父组件的东西(state),homelink是属于父组件

/>

</div>

</div>

</div>

);

}

}

export default App;

 

子组件Header.js

Jeff: 作为父组件的一个子部分,看似return出html就好了,没用到render。

        只需要显示,所以就搞成”无状态组件“就好啦,不需要render。

import React from 'react';

const Header = (props) => {

return (

<div className="container">

<div className="row">

<div className="col-xs-1 col-xs-offset-11">

<h1>{props.homeLink}</h1>

</div>

</div>

</div>

);

};

export default Header;

 

子组件Home.js

Jeff: 有交互,所以需要render。

import React, { Component } from 'react';

import PropTypes from 'prop-types';

export default class Home extends Component {

constructor(props) {

super(props);

this.state = {

age: props.initialAge,

status: 0,

homeLink: "Changed Link"    // (3) 兄弟子组件获得的改变值,也就是父组件为他俩搭的桥

}

setTimeout(() => {

this.setState({

status: 1

})

}, 3000)

}

onMakeOlder() {

this.setState({

age: this.state.age + 3

})

}

handleGreet() {

this.props.greet(this.state.age)

}

onChangeLink() {

this.props.changeLink(this.state.homeLink);  // (2) 执行了“指针函数” from 父组件;注意参数,子组件会将input的值拿来作为这里的value。

}

render() {

return (

<div className="container">

<div className="row">

<div className="col-xs-1 col-xs-offset-11">

<div>your name is {this.props.name}, your age is {this.state.age}</div>

<p>Status: {this.state.status}</p>

<button onClick={() => {this.onMakeOlder()}} className="btn btn-primary">Make me older</button>

<hr />

<button onClick={this.handleGreet.bind(this)} className="btn btn-primary">Greet</button>

<hr />

<button onClick={this.onChangeLink.bind(this)} class="btn btn-primary">Change Header Link</button>  // (1) 点击触发

</div>

</div>

</div>

);

}

}

Home.propTypes = {

name: PropTypes.string,

age: PropTypes.number,

user: PropTypes.object,

greet: PropTypes.func

};

 (13) 双向数据绑定

在以上的基础上,加了input框;

这里会读入“输入框”的内容,然后再改写Header的内容。

 

<input

  type ="text"

  defaultValue={this.props.initialName}

  value ={this.state.initialName}

  onChange ={(event) => this.onHandleChange(event)}  // (1) 只是改变了子组件home内部的一个state;奇怪,这里的event代表了什么?

/>

<button onClick={this.onChangeLink.bind(this)} className="btn btn-primary">Change Header Link</button> // (2) 点击后,读出这个home内部的state,然后改写父组件的state

event 代表了什么?event.target可获得事件涉及组件的属性。

  onHandleChange(event) {

this.setState({

homeLink: event.target.value

})

}

 

 

  (14) 组件生命周期 

git clone https://github.com/hfpp2012/hello-react.git

组件的代码复用

class Show中没有state,所以课省略掉constructor,系统会自动采用默认的。

下图也展示了组件的可复制性,方便代码复用。 

 

以上是 [React] 09 - Tutorial: components 的全部内容, 来源链接: utcz.com/z/381889.html

回到顶部