react

40、React

什么是React?

  • React 是一个用于构建用户界面的框架(采用的是MVC模式):集中处理VIEW这一层
  • 核心专注于视图,目的实现组件化开发

什么是组件化开发?
www.cr789.com_【官方首页】-皇冠现金网我们可以很直观的将一个复杂的页面分割成若干个独立组件,每个组件包含自己的逻辑和样式 再将这些独立组件组合完成一个复杂的页面。 这样既减少了逻辑复杂度,又实现了代码的重用

  • 可组合
  • 可重用
  • 可维护

安装react脚手架(跑环境)

在全局下安装create-react-app
打开命令行工具输入以下代码
npm install create-react-app -g
默认会自动安装React,react-dom两部分

使用react脚手架创建项目

需要在哪个文件夹下创建react项目就在那个项目下打开命令行工具输入以下代码
create-react-app xxx(xxx为项目名称:不能出现大写字母)
cd xxx(转到项目目录下)
yarn start或npm start启动项目即可

react和react-dom模块

在js文件的顶部导入react和react-dom
react:核心模块(生命周期等钩子函数都是在这里定义的)
react-dom:提供与DOM相关的功能,会在window下增加ReactDOM属性,内部比较重要的方法是render,将react元素对象或者react组件插入到页面中

import React from 'react';//核心基础 模块(生命周期)
//用来把react元素(jsx元素)渲染成真实的DOM结构并添加到页面当中
import ReactDOM,{render} from 'react-dom';
//ReactDOM中就一个方法比较常用叫render
//可以把render从react-dom中解构出来

ES6中的模块导入和导出

//=>A模块
import Temp,{lib} from "./B";//=>把导入的Temp中的部分属性方法进行解构赋值
new Temp().init(); //new Temp生成一个实例,并且执行它原型上的init方法
lib();//=>Temp.lib()

//=>B模块
export default class Temp {
    init() {
        console.log(`hello world`);
    }
    static lib(){//=>Temp.lib
        
    }
}

react中的jsx语法与render渲染方法

JSX语法

react构建页面有自己的语法:JSX语法;
jsx是一种JS和html混合的语法,HTML 语言直接写在 JavaScript 语言之中,不加任何引号,这就是 JSX 的语法,它允许 HTML 与 JavaScript 的混写;JSX最后会通过babel进行转义
jsx中的html部分和原生html基本一样(也有不一样的比如class要用className,label的for要用htmlFor),并且属性名要使用驼峰命名法;
用jsx语法写的内容叫做jsx元素(虚拟DOM元素);
jsx元素具有不变性 :只在页面初次加载的时候渲染一次,以后只能通过更改状态和属性来重新渲染;

jsx表达式的用法

  • 1、可以放JS的执行结果:在react中想将js当作变量引入到jsx中需要使用{}
  • 2.在jsx中,相邻的两个jsx元素 渲染时需要外面包裹一层元素(只能有一个根元素)
function build(str) {
    return <h1>{str.name}</h1><h1>{str.name}</h1>;//在jsx语法中,这种写法是错误的
}
- 3、如果多个元素需要在return后面换行,我们需要加一个'()'当作一个整体返回
- 4、可以把JSX元素当作函数的返回值
- 5、用`<`和`{}`来区分是html还是js表达式:遇到 HTML 标签(以` <` 开头),就用 HTML 规则解析;遇到代码块(以` { `开头),就用 JavaScript 规则解析
- 6、循环绑定需要给元素设置唯一的KEY属性(属性值是唯一的)

**`jsx属性的用法`**
在jsx中分为普通属性和特殊属性
- 1、普通属性:和html中的一样
- 2、特殊的属性如class,要使用className;for(label中的for)要使用htmlFor
- 3、style要采用对象的方式
```javascript

import React from 'react';
import ReactDOM,{render} from 'react-dom';
let str='<h1>纯标签</h1>';
let style={background:'red'};
render(<div>
<li className="aa"></li>
<li htmlFor="aa" style={style}></li>
<li dangerouslySetInnerHTML={{__html:str}}></li>
</div>,window.root);

- 4、dangerouslyInnerHTML插入html(基本上用不到)

React.createElement

  • React.createElement用来创建一个react对象,具有一个type属性代表当前的节点类型,还有节点的属性props;
React.createElement("div",null,"姜,",React.createElement("span",null,"帅哥"))
//结果如下
{type:'div',props:{className:"red",children:['姜',{type:'span',props:{id:'handsome',children:'帅哥'}}]}}

render():此方法是由react-dom模块中提供的方法

第一个参数为要渲染的jsx元素
第二个参数为要把渲染的DOM结构插入到哪个容器当中
第三个参数为渲染完成后需要执行的回调函数;

  • 用来将虚拟DOM元素(jsx元素)渲染成真实的DOM;
ReactDOM.render(<div className='app'>姜<span id='handsome'>帅哥</span></div>,window.root);

react中的虚拟DOM

我们在react中创建的组件和用jsx语法写出的标签等,都是虚拟的DOM元素:
组件并不是真实的 DOM 节点,而是存在于内存之中的一种数据结构,叫做虚拟 DOM (virtual DOM)。只有当它插入文档以后,才会变成真实的 DOM 。根据 React 的设计,所有的 DOM 变动,都先在虚拟 DOM 上发生,然后再将实际发生变动的部分,反映在真实 DOM上,这种算法叫做 DOM diff ,它可以极大提高网页的性能表现。

虚拟DOM渲染过程(虚拟DOM—>真实DOM)

1、构建虚拟DOM元素(jsx元素):

  • 在js中用jsx语法写的jsx元素就是虚拟DOM元素;(render中的元素)
    render(<h1 className='red'>心有猛虎,细嗅蔷薇</h1>,window.root)

2、调用render方法把虚拟DOM元素渲染成真实的DOM结构

  • 1)render方法会先用babel把jsx元素转化为ES5语法
    'h1', {className: 'red'}, '心有猛虎,细嗅蔷薇'
  • 2)再用React.createElement方法根据上面的结果创建出一个react对象
    React.createElement('h1', {className: 'red'}, '心有猛虎,细嗅蔷薇')结果如下
    {type: "h1", props: {className: "red", children: "心有猛虎,细嗅蔷薇"}}
  • 3)此时render方法将react对象转化为真正的DOM结构,渲染到页面上
    render(<h1 className='red'>心有猛虎,细嗅蔷薇</h1>,window.root)

自己模拟React.createElement方法与render方法:(render不完善)

function createElement(type,props,...children) {
if(children.length === 1) children = children[0];
return {type,props:{...props,children:children}}
}
let myRender = (obj, container,callback) => {
let {type, props} = obj;
let ele = document.createElement(type);//创建第一层
for (let key in props) {
if (key === 'children') {
//children可能是数组也可能是一个字符串(纯文本)
if ({}.toString.call(props[key]).slice(8,-1)==='Array') {
props[key].forEach(item => {
if (typeof item === 'object') {//如果子元素是对象,那就继续调用render
myRender(item, ele);
} else {
ele.appendChild(document.createTextNode(item));
}
})
} else {//字符串的话直接创建文本节点并插入到元素中
ele.appendChild(document.createTextNode(props[key]));
}
} else if (key === 'className') {//className要做单独处理
ele.setAttribute('class', props[key]);
} else {
ele.setAttribute(key, props[key]);//其他的直接设置(style等属性未做处理)
}
}
container.appendChild(ele);//把DOM元素插入到html中
callback && callback();//=>添加成功后触发回调函数执行
};

react中的组件

react元素是是组件组成的基本单位
组件有两种声明方式

1、第一种方式是函数声明

每一个创建的组件都是一个自定义的标签,在标签上设置的属性都会转换为react对象的props属性,当react渲染DOM的时候会把props传递给下面例子中的Build函数

import React from 'react';
import ReactDOM,{render} from 'react-dom';
let school1={name:'zfpx',age:10};
let school2={name:'珠峰',age:8};
let Build=function (props) {// "函数"(组件)的参数是属性
return <p>{props&&props.name}{props&&props.age}</p>;
};
render(<div>
<Build name={school1.name} age={school1.age}/>
<Build {...school2}/>//利用解构赋值将对象中的内容解构出来传递给Build组件
<h1></h1>
</div>,window.root);

###2、第二种是以类的形式声明
import React,{Component} from 'react';
import ReactDOM from 'react-dom';
let school1 = {name:'珠峰',age:8};
let school2 = {name:'珠峰',age:0};
//1.类声明的组件有this this上有props属性
class Build extends Component{//Component这个基类上有this.setState
    render(){ // 这个组件在调用时默认会调用render方法(此render相当于注册组件)
        let {name,age} = this.props;
        return <p>{name} {age}</p>
        // return  <p>{this.props.name} {this.props.age}</p>
    }
}
ReactDOM.render(
此render的渲染步骤
1)先渲染外层div,将jsx元素转化为ES5的语法,然后调用React.createElement方法创建react对象,当遇到div下的子元素的时候,会进行以下操作
//1、发现Build 是一个继承Component的类,它会创建这个类的一个实例,并且执行原型上的render方法
//2、执行原型上的render返回JSX元素,然后继续执行外层的render
2)继续将上面返回的jsx元素转化为es5语法,调用React.createElement方法创建虚拟DOM元素对象,Build 这个位置被返回的元素对象替换
3)外层ReactDOM.render将上面生成的react对象渲染成真实的DOM结构,并追加到页面中;
<div>
    <Build name={school1.name} age={school1.age}/>
    <Build {...school2} />
</div>,window.root);

区别:类声明有状态,this,和生命周期
声明组件的规则:

  • 1、首字母必须大写,目的是为了和JSX元素进行区分
  • 2、组件定义后可以像JSX元素一样进行使用
  • 3、可以通过render方法将组件渲染成真实DOM(做了优化 只会重新渲染改变的地方)

单闭合标签不能添加子节点;双闭合标签可以

组件中属性(props)和状态(state)的区别

属性和状态的变化都会影响视图更新,但是两者导致视图刷新的机制是不一样的;

  • 1、确切的说不是因为props更新了导致的视图刷新,而是传递属性的组件(父组件)重新的渲染了,从而导致接受属性的组件(子组件)重新渲染,这样属性就也随着更新了;
  • 2、状态导致的视图刷新是只要通过this.setState改变状态,视图就会刷新;

组件的数据来源有两个地方:

1、通过props(属性) 获取数据:

外界传递进来的(存在默认属性和属性校验),是只读的,不能修改;
有属性校验模块:prop-types(用来校验属性的类型和是否必须设置某属性)
用yarn add prop-types来安装。用来校验属性的

import React,{Component} from 'react';
import ReactDom,{render} from 'react-dom';
import PropTypes from 'prop-types';
// 1、默认的属性必须写在defaultProps中(不能改名字)
// 2、校验器的名字必须是propTypes(不能改名字);
class School extends Component{//类上的属性(把类当作对象添加的属性)就叫做静态属性
static defaultProps={//会先默认调用defaultProps
name:'珠峰',
age:1
};
static propTypes={//校验属性的值(number)类型和是否必须设置该属性(isRequired)
age:PropTypes.number.isRequired
};
constructor(props){
//如果想在constructor中使用props,则只能传递参数props,在constructor外面可以直接用this.props
//不能在组件中更改属性
super();
console.log(this.props)//undefined
}
render(){//渲染组件的,返回一个组件
//如果没有默认的defaultProps,也没有传递props,则this.props是一个空对象
return <h1>{this.props.name} {this.props.age}</h1>
}
}
//name={'珠峰培训'}:这样写大括号中就可以是变量
render(<School name={'珠峰培训'} age={9}/>,window.root);

####2、this.state(状态) 状态是自己的:
是可读写的(写了状态就必须给默认值);
我们可以通过this.setState({})方法来修改状态,由于修改状态会引起视图刷新(即更新DOM),而有关DOM的操作都是异步的,所以this.setState({})是异步的;
```javascript

import React,{Component} from 'react';
import ReactDom,{render} from 'react-dom';
//1、state变化可以更新视图,只能通过this.setState({})来更改状态,调用后会更新视图
class Counter extends Component{
constructor(){
super();
console.log(this.state);//默认为undefined
this.state={count:0}
}
add=(val)=>{ //val后面省略了一个参数e(事件对象),是onClick事件默认传进来的,放在val的后面,因为bind的传参机制
//setState方法会默认按最后的设置进行执行
// this.setState({count:this.state.count+val});
this.setState({count:this.state.count+val});//会执行这个
//setState有两种写法,一种是传入对象的方式,一种是传入函数的方式
//下一个状态是依赖于上一个状态时,需要写成函数的方式(回调函数)
this.setState(prevState=>({count:prevState.count+val}));//如果返回的就是一个对象,则需要用'()'包裹.如果不用小括号包裹,则需要加return。因为函数如果不加return或者return后什么也不写,函数的返回值则为undefined
}); //等同于下面这种写法
//this.setState({count:this.state.count+val},function () {
// this.setState({count:this.state.count+val});
//};
render(){
return <p>
{this.state.count}
/因为add方法是在类里面声明的,属于ES7语法,所以 add方法中的this是指向当前实例的,所以bind不需要改变this指向/
<button onClick={this.add.bind(null,2)}>+</button>
</p>
}
}
render(<Counter/>,window.root);

复合组件与数据传递

复合组件:将多个组件进行组合嵌套;
复合组件之间的数据传递:

1、<font color=red>父传子</font>:父组件传递给子组件数据通过设置属性的方式

import React,{Component} from 'react';
import ReactDom,{render} from 'react-dom';
import PropTypes from 'prop-types';
import 'bootstrap/dist/css/bootstrap.css';
//1、什么是复合组件,将多个组件进行组合
//2、结构非常复杂时可以把组件分离开
//3、复合组件 有父子关系,父的数据传递给子(通过给子组件设置属性)
// Panel组件 Heading Body
class Counter extends Component{
render(){
//由于在Counter中传递了属性,所以这里可以用this.props获取到;
let {header:header,body:body}=this.props;
return (
<div className='container'>
<div className='panel-default panel'>
//这里写了之后就可以在下面的子组件中拿到对应的props值
<Header header={header}></Header>
<Body body={body}></Body>
</div>
</div>
)
}
}
//react中需要把属性一层一层向下传递:单向数据流;
class Header extends Component{
render(){
return (
<div className='panel-heading'>
//上面传递了之后就可以直接在此处使用this.props属性了;
{this.props.header};
</div>
)
}
}
class Body extends Component{
render(){
return (
<div className={'panel-body'}>
{this.props.body}
</div>
)
}
}
let data={header:'我很帅',body:'长的英俊'};
render(<Counter {...data}/>,window.root);

####2、**<font color=red>子传父</font>:**通过父亲传递给儿子一个函数,儿子调用父亲的函数将要改的值传递给父亲,父亲更新值,刷新视图;
```javascript

import React, {Component} from 'react';
import ReactDom, {render} from 'react-dom';
import 'bootstrap/dist/css/bootstrap.css';
//1、子传父 通过父亲传递给儿子一个函数,儿子调用父亲的函数将要修改的值传递给父亲,父亲更新值,刷新视图;
class Counter extends Component {
constructor() {
super();
this.state = {color: 'primary'};
}
changeColor = (color) => {
this.setState({color});
};
render() {
let {header} = this.props;
return (
<div className='container'>
<div className={'panel panel-' + this.state.color}>
<Header header={header} color={this.changeColor}></Header>
</div>
</div>
)
}
}
//react中需要把属性一层一层向下传递:单向数据流;
class Header extends Component {
clickColor = () => {
this.props.color('danger');//儿子调用父亲的方法,把要改的值传递给父亲,让父亲自己修改
};
render() {
return (
<div className='panel-heading'>
{this.props.header}
<button className={btn btn-danger} onClick={this.clickColor}>改颜色</button>
</div>
)
}
}
let data = {header: '我很帅'};
render(<Counter {...data}/>, window.root);

ref属性

ref可以写在组件上也可以写在DOM元素上,获取的时候得到两者的结果是不同的;

  • 写在组件上时(这里的组件是有状态的组件),通过refs获取的是组件的实例;
  • 写在DOM元素上时,通过refs获取的是具体的DOM元素节点.(获取的是真实DOM)

ref的值可写为两种形式

1、直接写为一个值

通过ref设置的属性:可以通过this.refs.xxx获取到对应ref值为xxx的dom元素或者组件的实例

2、写成一个函数的形式

写成函数时,这个函数将会在渲染成真实DOM结构或组件被挂载后执行,函数的参数为真实的DOM结构或组件的实例

//如上面非受控组件中的例子
//1、直接将ref写为一个值
<input type="text" ref='a'/>
//2、将ref写为一个函数
<input type="text" ref={(x)=>this. c=x}/>
{/* x 代表真实的DOM(写成函数的形式会默认传入当前设置ref的DOM结构 ),上面这种写法相当于把DOM结构挂载到了this的自定义属性c上,以后可以直接用this.c拿到这个input的DOM结构*/}
resultChange=()=>{//通过ref设置的属性:可以通过this.refs.xxx获取到对应ref值为xxx的dom元素
this.c.value=parseFloat(this.refs.a.value)+parseFloat(this.refs.b.value);
};

受控组件与非受控组件(受状态控制)

组件是否与this.state有关系区分为受控与非受控组件,受控组件可以赋予默认的值

受控组件

需要与onInput/onChange/disabed/readOnly等控制value/checked的属性或事件一起使用

import React, {Component} from 'react';
import ReactDom, {render} from 'react-dom';
class Input extends Component {
//1、受状态控制的组件,必须要有onChange方法,否则不能使用;
//2、受控组件可以赋予默认值(官方推荐使用受控组件)
constructor() {
super();
this.state = {val: '', a: 1, b: 2};//初始化状态:设置默认值
}
inputChange = (val, e) => {//处理多个输入框的值映射到this.state的方法
//val:要修改的state中的属性的名称
//e:事件源(e.target.value是要修改成的值)
this.setState({[val]: parseFloat(e.target.value)||0});//更改状态
};
render() {
return (
<div>
//下面两个input都受this.state影响(受控组件)
<input type="text" value={this.state.a} onChange={this.inputChange.bind(null, 'a')}/>
<input type="text" value={this.state.b} onChange={e =>{
//方法执行的时候要用到事件对象e,所以必须要传
this.inputChange('b', e)
}}/>
{this.state.a + this.state.b}
</div>
)
}
}
render(<Input/>, window.root);

####非受控组件(基于ref来管理)
```javascript

import React,{Component} from 'react';
import ReactDom,{render} from 'react-dom';
class Input extends Component{
//输入框value值不受状态控制,不能初始化默认值
resultChange=()=>{//通过ref设置的属性:可以通过this.refs.xxx获取到对应ref值为xxx的dom元素
this.c.value=parseFloat(this.refs.a.value)+parseFloat(this.refs.b.value);
};
render(){
return (
<div onChange={this.resultChange}>
<input type="text" ref='a'/>
<input type="text" ref='b'/>
{/x代表真实的DOM(写成函数的形式会默认传入当前设置ref的DOM结构),下面这种写法相当于把元素挂载到了this的自定义属性c上/}
<input type="text" ref={(x)=>this.c=x}/>
</div>
)
}
}
render(<Input/>,document.getElementById('root'));

react元素中的事件绑定

给react元素(jsx元素)事件绑定对应的方法时,方法如果不做处理,那么执行的时候方法中的this默认是undefined
可通过如下方式解决:

  • 1、使用bind方式;(绑定的时候处理)

  • 2、使用ES6的箭头函数(绑定的时候处理:在外层套一层箭头函数)

  • 3、使用ES7的箭头函数(在类中定义的时候处理:直接写为xxx=()=>{} )


Class中声明的方法this默认不绑定到实例上,需要手动绑定.ES6语法规范;
www.cr789.com_【官方首页】-皇冠现金网在Class中利用箭头函数声明的方法,被规定为ES7语法,方法中的this都是当前实例。

React中的生命周期

在react操作过程中,设定了很多的执行阶段,每个阶段提供了不同方法,
[图片上传失败...(image-a06d22-1541658838762)]

[初期渲染]

defaultProps:

  • 默认属性设置与校验

constructor:

  • 获取属性和设置默认初始状态

componentWillMount:(只执行一次)

  • constructor执行完成后,DOM结构开始加载之前,执行此方法,

render(开始渲染)

componentDidMount(只执行一次)

  • DOM结构加载完成后会自动触发此函数

[状态发生改变时触发]

shouldComponentUpdate

  • 状态更新时会触发此方法,返回布尔值

componentWillUpdate

  • 如果shouldComponentUpdate返回true则执行此函数,否则不执行

render(开始渲染)

componentDidUpdate

  • 组件完成更新后触发此方法

[卸载组件时触发]

ReactDOM.unmountComponentAtNode

  • 删除某个组件

componentWillUnmount(组件将要卸载)

  • 组件移除时会调用此方法,一般在这个方法中我们要清除定时器

[属性发生改变时触发]

componentWillReceiveProps

import React, {Component, PureComponent} from 'react';
import ReactDom, {render} from 'react-dom';
class Counter extends Component {/PureComponent是纯组件:比较的是状态的地址,如果是同一个地址,则不会更新,所以状态最好采用新的状态替换掉老的/
static defaultProps = {
name: 'zfpx'
};
constructor(props) {
super();
this.state = {number: 0};
console.log('1、constructor');
}
componentWillMount() {//同步代码放在此处最好:如获取本地的数据:在渲染之前获取数据,只渲染一次
console.log('2、父组件将要加载');
}
componentDidMount() {
this.setState({number: this.state.number + 1});
console.log('5、父组件已经挂载完成');
}
click = (e) => {
this.setState({number: this.state.number + 1});
};
//react可以在shouldComponentUpdate方法中优化 PureComponent 可以帮我们做这件事
shouldComponentUpdate(nextProps, nextState) {//分别代表下一次的属性和下一次的状态
//组件是否需要更新
console.log('父组件是否需要更新');
return nextState.number % 2;//如果此函数返回了false,就不会调用render方法了
}
//乱用this.setState({})会造成栈溢出
componentWillUpdate() {
console.log('父组件将要更新');
}
componentDidUpdate() {
console.log('父组件完成更新');
}
render() {
console.log(3、父组件render(渲染));
return (
<div>
<p>{this.state.number}</p>
<ChildCounter n={this.state.number}/>
<button onClick={this.click}>+</button>
</div>
)
}
}
class ChildCounter extends Component {
componentWillReceiveProps(newProps) {//第一次不会执行,之后属性更新时才会执行
/父组件渲染完成后发现有状态更新,执行componentWillReceiveProps,接着执行shouldComponentUpdate,然后子组件渲染/
/componentWillReceiveProps只写在子组件中/
console.log('子有新的属性');
}
render() {
console.log('4、child render');
return (
<div>
{this.props.n}
</div>
)
}
shouldComponentUpdate(nextProps, nextState) {
console.log('子组件是否需要更新');
return nextProps.n % 3;
}
}
render(<Counter name={'计数器'}/>, window.root);

###<font color=red>**注意**</font>:
**最好不要在任何一个状态更新时触发的方法中写**`this.setState()`,如果不加条件控制的话,会再次触发状态更新而导致方法执行,这样就会造成栈溢出;
```javascript

/页面初始化时触发/
//defaultProps:
//constructor
//componentWillMount(将要挂载)页面初次加载时执行1次
//render
//componentDidMount(完成挂载)页面初次加载完成时执行1次
/状态更新时会触发/
//shouldComponentUpdate
//componentWillUpdate
//render
//componentDidUpdat
/属性更新/
//componentWillReceiveProps
/卸载/
// componentWillUnmount

获取组件的所有子节点

在函数式声明的组件中我们用props.children来获取组件的所有子节点;
在以类声明的组件中我们用this.props.children来获取组件的所有子节点;

render(<Dinner>

推荐阅读更多精彩内容