React

vr# 第1章:React入门
英文官网
中文官网

1.1.React简介

1.1.1.介绍描述
  1. 用于动态构建用户界面的 JavaScript 库(只关注于视图)

  2. 由Facebook开源

实现一个页面需求通常有三步:
1.发送请求获取数据
2.处理数据(过滤、整理格式等)
3.React就是操作DOM呈现页面
是一个将数据渲染为HTML视图的开源 JavaScript 库。

1.1.2.React的特点
  1. 采用组件化模式声明式编码,提高开发效率及组件复用率
    命令式编码:获取节点再改样式;
    声明式编码:用特殊语法表达更改的样式,react会帮我们操作dom
  2. React Native 编写原生应用,进行移动端开发
  3. 使用虚拟DOM+优秀的Diffing 算法,尽量减少与真实DOM交互
1.1.3.React高效的原因
  1. 使用虚拟(virtual)DOM, 不总是直接操作页面真实DOM。
  2. DOM Diffing算法, 最小化页面重绘。

原生JavaScript操作DOM繁琐、效率低(DOM-API操作 UI)
使用JavaScript直接操作DOM,浏览器会进行大量的重绘重排
原生JavaScript没有组件化编码方案,代码复用率低


1.2.React的基本使用

1.2.1.效果

在这里插入图片描述

1.2.2.相关js库
  • react.development.js:React核心库。
  • react-dom.development.js:提供操作DOM的react扩展库。
  • babel.min.js:解析JSX语法代码转为JS代码的库。
    <!--  引入react核心库  -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!--  引入react-dom,用于支持react操作DOM  -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!--  引入babel,用于将jsx转为js  -->
    <script type="text/javascript" src="../js/babel.min.js"></script>
1.2.3.创建虚拟DOM的两种方式
  1. 纯JS方式(一般不用)
    在这里插入图片描述

  2. JSX方式
    在这里插入图片描述

1.2.4.虚拟DOM与真实DOM

在这里插入图片描述

在这里插入图片描述

  1. 虚拟DOM本质是object类型的对象(一般对象)
  2. 虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在用,无需真实DOM上那么多的属性。
  3. 虚拟DOM最终会被React转化为真实DOM,呈现在页面上。
// js创建虚拟节点
React.createElement(标签名,标签属性,标签体内容)

1.3.React JSX

在这里插入图片描述
全称: JavaScript XML
react定义的一种类似于XML的JS扩展语法: JS + XML
本质是React.createElement(component, props, …children)方法的语法糖

1.3.1.作用:用来简化创建虚拟DOM
  • 写法: var ele = <h1>Hello JSX!</h1>
  • 注意1:它不是字符串, 也不是HTML/XML标签
  • 注意2:它最终产生的就是一个JS对象
1.3.2.基本语法规则
  1. 定义虚拟DOM时,不要写引号。
    在这里插入图片描述

  2. 标签中混入Js表达式时要用{}。
    在这里插入图片描述

  3. 样式的类名指定不要用class,要用className。
    在这里插入图片描述

  4. 内联样式,要用style={{key:value}}的形式去写
    在这里插入图片描述

  5. 只有一个根标签
    在这里插入图片描述

  6. 标签必须闭合
    在这里插入图片描述

  7. 标签首字母
    若小写字母开头,则将改标签转为htm1中同名元素,若htm1中无该标签对应的同名元素,则报错。
    若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。

    在这里插入图片描述

1.3.3.js中的表达式和语句区分

在这里插入图片描述
为什么这段代码会报红呢?
因为在react {}包裹的必须是js表达式才可以,for循环是语句,不是表达式

  • 表达式:一个表达式会产生一个值,左侧用一个变量能接到值就是表达式,可以放在任何一个需要值的地方
    • (1).a
    • (2).a+b
    • (3)demo(1) 如果函数没有返回值会自动返回underfined,所以也是能接到值的
    • (4)arr.map()
    • (5).function test(){}
  • 语句(代码),控制代码走向,没有值
    • (1).if(){}
    • (2).for(){}
    • (3).switch(){case:xxxx}

所以上面的代码改用map方法并返回就没问题了
在这里插入图片描述
注意:data数据是数组的形式react会帮我们遍历,如果是对象则不行,控制台报错
在这里插入图片描述
在这里插入图片描述

1.3.4.babel.js的作用

1)浏览器不能直接解析JSX代码, 需要babel转译为纯JS的代码才能运行
2)只要用了JSX,都要加上type=“text/babel”, 声明需要babel来处理

<body>
    <!--  准备容器  -->
    <div id="test"></div>

    <!--  引入babel,用于将jsx转为js  -->
    <script type="text/javascript" src="../js/babel.min.js"></script>

    <script type="text/babel">  /* 此处一定要写babel */
        // 1.创建虚拟DOM
        const VDOM = <h1>Hello,React</h1>/* 此处一定不要写引号,因为不是字符串 */
        // 2.渲染虚拟DOM到页面
       // ReactDOM.render(VDOM,document.getElementById('test'))
    </script>
</body>
1.3.5.渲染虚拟DOM(元素)

语法: ReactDOM.render(virtualDOM, containerDOM)
作用: 将虚拟DOM元素渲染到页面中的真实容器DOM中显示
参数说明
1)参数一: 纯js或jsx创建的虚拟dom对象
2)参数二: 用来包含虚拟DOM元素的真实dom元素对象(一般是一个div)

<body>
    <!--  准备容器  -->
    <div id="test"></div>

    <!--  引入react核心库  -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!--  引入react-dom,用于支持react操作DOM  -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!--  引入babel,用于将jsx转为js  -->
    <script type="text/javascript" src="../js/babel.min.js"></script>

    <script type="text/babel">  /* 此处一定要写babel */
        // 1.创建虚拟DOM
        const VDOM = <h1>Hello,React</h1>/* 此处一定不要写引号,因为不是字符串 */
        // 2.渲染虚拟DOM到页面
        ReactDOM.render(VDOM,document.getElementById('test'))
    </script>
</body>

1.4.模块与组件、模块化与组件化的理解

1.4.1.模块

把庞大的js文件拆分成多份

  1. 理解:向外提供特定功能的js程序, 一般一个js文件就是一个模块
  2. 为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂。
  3. 作用:复用js, 简化js的编写, 提高js运行效率
1.4.2.组件

页面某个能复用的区域,将其封装成组件,就是该区域的所有资源代码功能全部打包到一个组件里

  1. 理解:用来实现局部功能效果的代码和资源的集合(html/css/js/image等等)
  2. 为什么要用组件: 一个界面的功能更复杂
  3. 作用:复用编码, 简化项目编码, 提高运行效率
1.4.3.模块化

当应用的js都以模块来编写的, 这个应用就是一个模块化的应用

1.4.4.组件化

当应用是以多组件的方式实现, 这个应用就是一个组件化的应用

第2章:React面向编程

2.1基本理解和使用

2.1.1使用React开发工具调试

在谷歌浏览器安装React调试工具

  1. 打开谷歌浏览器进入谷歌商店
    在这里插入图片描述
  2. 搜索 React Developer Tools,添加到Chrome
    在这里插入图片描述
  3. 在扩展程序中查看
    在这里插入图片描述
  4. 将该工具固定在浏览器上方(绿色框的图标表示当前网页是否用React编写的,如果是图标则会变亮)

在这里插入图片描述 图标橙色,没有经过打包,开发者模式
在这里插入图片描述 正常图标,打包并且部署到服务器上线了

在这里插入图片描述
5. 开发者模式使用该工具
F12,点击展开箭头
Compontent:网页由多少个组件组成,每个组件拥有的属性
Profiler:记录网站的性能,渲染时间,组件加载时间
在这里插入图片描述

2.1.2定义组件
  1. 函数式组件
<script type="text/babel">
    // 1.创建函数式组件(组件名首字母要大写)
    function MyCompontent(){
    	console.log(this) //undefined
        return <h2>我是函数式组件</h2>
    }
    // 2.渲染组件到id为test的标签内部
    ReactDOM.render(<MyCompontent/>,document.getElementById('test'))
</script>

在函数组件中如果打印this,改this是undefined,而不是像我们写js函数一样,this指向window,原因是jsx代码需要经过babel翻译,babel翻译会开启严格模式,禁止自定义函数里的this指向window。试一试

执行了ReactDOM.render(<MyCompontent…之后,发生了什么?

  • React解析组件标签,标签为大写,找到对应的MyCompontent组件
  • 发现组件是使用函数定义的,随后调用该函数(我们并没有调用函数,是react帮我们调的),将返回的虚拟DOM转为真实DOM,呈现在页面上
  1. 类式组件
<script type="text/babel">
    // 1.创建类式组件
    class MyComponent extends React.Component{
        //render是放在MyComponent的原型对象上
        render() {
            console.log(this) //谁调用this指向谁,此处指向MyComponent的实例对象,也叫组件实例对象
            return <h2>我是类式组件</h2>
        }
    }
    // 2.渲染组件到指定容器中
   ReactDOM.render(<MyComponent/>,document.getElementById('test'))
</script>

注意:必须要继承React.Component父类,必须要写render方法,而且render要有返回值

执行了ReactDOM.render(<MyCompontent…之后,发生了什么?

  • React解析组件标签,标签为大写,找到对应的MyCompontent组件
  • 发现组件是类定义的,随后React帮我们new出来该类的实例对象,并通过实例对象调用原型对象中的render方法
  • 将render返回的虚拟DOM转为真实DOM,随后呈现在页面上

原型链知识点复习:实例对象中都有_proto_(对象原型)属性,该属性指向prototype(原型对象),原型对象中存有类的共享的属性和方法其中的constructor属性指向该类的构造函数,如果在实例对象中的原型对象中找不到对应的方法,则会继续往上查找,直到找到原型对象是Object的为止,如果Object中也没有找到该方法,则会返回null

  1. 简单组件与复杂组件
    有状态(state)的组件为复杂组件,反之为简单组件

2.2. 组件实例三大核心属性state

state是在组件的实例对象中的
在这里插入图片描述

2.2.1状态的基本使用

创建一个天气类 Weather,该类继承自 React.Component,则该类的实例身上会有 props、refs、state等的属性。

state 作为一个对象在我们的项目中使用,用于存储类组件内部的数据。

在类Weather 的构造器中,使用this.state 可以获取到 state。初始时state 为 null,我们在构造器中将 state 定义为对象类型,并为其指定若干属性。

<script type="text/babel">
    // 1.创建组件
    class Weather extends React.Component{
        // 构造器
        constructor(props) {
            super(props)
            // 借助构造器初始化状态
            this.state = {isHot:true}
        }
        render(){
            // 读取状态
            const {isHost} = this.state
            return <h1 onClick={demo}>今天天气很{ isHost?'炎热':'凉爽' }</h1>
        }
    }
    // 2.渲染组件
    ReactDOM.render(<Weather/>,document.getElementById('test'))

    function demo(){
        console.log('标题被点击了')
    }
</script>

事件绑定: 如果demo函数写了括号,如onClick={demo()},此时demo()是一个表达式,表示调用该函数,函数没有返回值会返回undefined,相当于onClick=undefined,把undefined返回给onClick作为回调,这样的话对应的事件会没有任何效果,所以实际绑定的时候函数不需要写括号

2.2.2类中方法中this指向
  1. 自定义函数this指向
    babel翻译开启了严格模式,禁止自定义函数指向window,所以this指向的是undefined
    在这里插入图片描述
  2. 类中的方法this指向
    解决这个问题需要将自定义函数,改写成类中的方法,将function去掉,代码写到类中,
    在这里插入图片描述
    但是此时控制台还是报同样的错误this还是undefined,原因是onClick是直接调用(window调用)的,changeWeather是作为onclick的回调也是直接调用(window调用),不是实例对象调用,再加上类中的方法默认开启了局部的严格模式,this不允许指向window,所以this是undefined
    解析:通过this将找到weather实例对象原型上的方法,作为onclick的回调,实际上就是吧changeWeather方法作为属性赋值给了onclick,然后直接调用(window调用)的onclick,相当于点击h1标签和直接调用方法而不是通过实例对象调用,类中的方法默认会开启严格模式,不是实例对象调用,this会指向undefined,可以看下面js代码的示例来理解
    在这里插入图片描述
  3. 解决类中this指向undefined问题
    使用bind方法,将this指向更正为实例对象,并赋值到实例对象自身的方法
    类中的方法是放在实例对象的原型对象上的,实例对象本身是没有该方法的
    在这里插入图片描述

在这里插入图片描述

2.2.3更改状态setState
  1. 在构造器中声明setState
    通过原型对象找到React.Component组件上的setState()方法进行更改,React不能直接赋值给state更改
    注意:更新状态的逻辑不是整个对象覆盖,而是同名的替换,没有同名的属性保持不变
 class Weather extends React.Component{
	constructor(props) {
	     super(props)
	     this.state = {isHot:true, wind:'微风'}
	     this.changeWeather = this.changeWeather.bind(this)
	}
	changeWeather(){
		let isHot = this.state.isHot
        // 状态必须通过setState就行修改是一种合并的操作,同名的替换掉,没有同名的则保留,setState是React.Component组件上的方法
        this.setState ({isHot: !isHot})
        // 严重注意:状态不可直接更改,下面这行就是直接更改
        // this.state.isHot = !isHot// 错误写法!!
	}
}
  1. 类中方法执行次数分析
    • 构造器调用次数:1次
      在ReactDOM.render渲染组件时,会帮我们new出Weather组件的示例对象,同时调用构造器,new了几次就调用了几次构造器
    • render调用次数:1+n次,1是初始化次数,n是状态更新次数
      执行过程先调构造器,构造器调完了实例对象才出来,实例对象出来了才能掉render
    • changeWeather调用次数:点击的次数
      调用的是自身的changeWeather 而不是原型上的changeWeather
 class Weather extends React.Component{
	constructor(props) { //调用1次
	     super(props)
	     this.state = {isHot:true, wind:'微风'}
	     this.changeWeather = this.changeWeather.bind(this)
	}
	changeWeather(){ //点击了几次就调用几次
		let isHot = this.state.isHot
        this.setState ({isHot: !isHot})
	}
	render(){ //调用1+n次
            const {isHot,wind} = this.state
            return <h1 onClick={this.changeWeather}>今天天气很{ isHot?'炎热':'凉爽' },{wind}</h1>
        }
}
ReactDOM.render(<Weather/>,document.getElementById('test'))
  1. state简写方式
    不用把状态写在构造器中
    类中方法使用箭头函数,箭头函数没有自己的this,会往上一层找,则函数内的this指向的就是这个类的实例,不需要用bind再来改变this指向了
    class Weather extends React.Component{
        //初始化状态,直接在类中给state赋值
        state = {isHot:true, wind:'微风'}
        //类中自定义方法————要用赋值语句+箭头函数的形式
        changeWeather = () => { //箭头函数没有自己的this,就会往上一层找this
            let isHot = this.state.isHot
            this.setState ({isHot: !isHot})
            console.log(this.state.isHot)
        }
        render(){
            const {isHot,wind} = this.state
            return <h1 onClick={this.changeWeather}>今天天气很{ isHot?'炎热':'凉爽' },{wind}</h1>
        }
    }
2.2.3state总结
  1. state是组件对象最重要的属性, 值是对象(可以包含多个key-value的组合)
  2. 组件被称为"状态机", 通过更新组件的state来调用render方法更新对应的页面显示(重新渲染组件)
  3. 组件中render方法中的this为组件实例对象
  4. 组件自定义的方法this为undefined,如何解决?
    • 强制绑定this: 通过函数对象的bind()
    • 箭头函数
  5. 状态数据,不能直接修改或更新,必须要借助setState来更改

2.3 组件实例三大核心属性props

每个组件对象都会有props(properties的简写)属性
组件标签的所有属性都保存在props中

2.3.1 props基本使用

在组件标签中设置属性并赋值,将会添加到props对象中,在组件类中就能使用this.props.属性名来获取到对应属性的值。

注意,props 是只读的,在类中不允许修改。

//创建组件
class Person extends React.Component{
	render() {
		// 拿到 props 中想要的属性,并用其渲染
		const {name, age, sex} = this.props
        	return (
	             <ul>
	                 <li>{name}</li>
	                 <li>{age}</li>
	                 <li>{sex}</li>
	             </ul>
                )
     }
}
// 在组件标签中设置的属性会添加到props对象中
ReactDOM.render(<Person name="jerry" age="18" sex="女"/>,document.getElementById('test'))
ReactDOM.render(<Person name="tom" age="22" sex="男"/>,document.getElementById('test2'))
2.3.2 批量传递props

使用展开运算符批量传递props,在react中,因为babel翻译+react允许展开运算符展开一个对象(原生js展开运算符不能展开对象),但是不能随意使用,仅仅适用于标签属性的传递

const person = {name:'张三' ,age: '18',sex: '男'}
ReactDOM.render(<Person {...persons} sex="女"/>,document.getElementById('test'))

原生js展开运算符,展开对象时会报错
在这里插入图片描述
展开运算符不能展开对象,
ES6新语法是使用展开运算符+{},构造一个新对象,一般用来浅拷贝对象
在这里插入图片描述
原生js和react, {}+展开运算符的区别
react中{…person}花括号的含义是里面需要写js表达式,
而原生js{…person}花括号的含义是ES6的语法

2.3.3 对props限制

需要引入props-types库,对组件标签属性进行限制
在这里插入图片描述

  1. 类型限制
    类名.propTypes ={
        PropTypes.xxx
    }
//对标签属性进行类型、必要性限制
Person.propTypes = {
    name: PropTypes.string, //声明类型
    age: PropTypes.number 
    speack: PropTypes.func //声明speack是函数类型,此处使用的是func,不能使用function,因为function的定义函数的关键字
}

如果属性是字符串,可以直接用引号的方式赋值
如果是数值类型(age)或者是函数类型(speak)的,需要用一对花括号进行包裹在这里插入图片描述

  1. 必要性限制
    PropTypes.xxx.isRequired
//对标签属性进行类型、必要性限制
Person.propTypes = {
    name: PropTypes.string.isRequired, //必要性限制
    age: PropTypes.number 
}
  1. 默认值
    类名.defaultProps ={
         键:值
    }
//指定标签属性默认值
Person.defaultProps = {
	sex: '男', //sex默认值为男
	age:18//age默认值为18
}

注意:props是只读的不能修改
在这里插入图片描述

2.3.4 props简写方式

将props对标签属性的限制写在类的内部,使用static修饰,加到类的自身可以直接用类名调用,如果不写static就是加给了类的实例对象
在这里插入图片描述

2.3.5 构造器中props

构造器是否需要传递props取决于,构造器中是否需要通过this访问props,如果需要则必须要传递props否则会报错

//这种场景很少见
constructor(props) {
    super(props);
    //如果在构造器中想要通过实例取到props(this.props),必须要在constructor和super中传递props
    console.log('constructor',this.props) 
}
constructor() {
    super();
    //constructor和super中没有传递props,则以下代码会this.props会是undefined
    console.log('constructor',this.props) //打印constructor undefined
    
}
2.3.6 函数式组件使用props

函数式组件只能使用props(不能使用state),对标签属性的限制也只能写在外侧

2.4 组件实例三大核心属性refs和事件处理

2.4.1 ref理解

组件内的标签可以定义ref属性来标识自己(相当于原生js中的id)好处是不需要再获取节点
原生js写法:
在这里插入图片描述
react中的ref写法

在这里插入图片描述
在控制台打印当前实例对象this时,可以看到refs属性的值是一个对象,键表示ref的名称,值表示ref当前处在的节点,如input节点(input整个html标签),refs可以收集多个ref
在这里插入图片描述

2.4.2 字符串形式ref

分别使用点击事件和失去焦点事件获取文本框的值,先给标签绑定ref属性,在事件的方法中通过实例对象的refs属性获取当前ref所在的节点
不推荐使用,写的多了效率可能会变低

<script type="text/babel">
	class Demo extends React.Component{
	    //展示左侧输入框数据
	    showData = ()=>{
	        const {input1} = this.refs
	        alert(input1.value)
	        console.log(this)
	    }
	    //展示右侧输入框数据
	    showData2 = ()=>{
	        const {input2} = this.refs
	        alert(input2.value)
	    }
	    render(){
	        return(
	            <div>
	                <input ref="input1" type="text" placeholder="点击提示数据"/>
	                <button onClick={this.showData}>点击提示左侧数据</button>
	                <input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
	            </div>
	        )
	    }
	}
	ReactDOM.render(<Demo/>, document.getElementById('test'))
</script>
2.4.3 内联函数回调形式的ref
  1. 执行过程:
    React渲染页面时先new Demo的实例,通过实例帮我们调用render方法,调用的同时会执行return里面jsx语法,同时也就帮我们触发了ref函数的执行(只有ref属性的回调函数React才会帮我们调用其他的不会),并把ref当前所处的节点(currentNode)传入,如图所示控制的打印的就是当前input节点在这里插入图片描述

  2. 基本使用
    一般使用时,会将这个节点挂载到实例对象的某个属性上

//this是在render方法中,render的this是当前的实例对象
<input ref={(currentNode)=>{this.input1 = currentNode}} type="text" placeholder="点击提示数据"/>

  1. ref调用次数
    在更新过程中(更新状态才会重新调用render)会执行两次,第一次传参数null,第二次传入参数DOM元素。这是因为每次渲染时会创建新的函数实例,所有react清空旧的ref并设置新的,定义成class绑定的形式可以避免这个问题,但是大多数情况下是无关紧要的
    • 下面代码中页面第一次加载时会自动调用render一次并出发ref的内联回调函数,所以控制台输出了当前ref节点
      在这里插入图片描述
    • 当更新状态后,控制台变化,会先打印一个null,再打印当前节点
      在这里插入图片描述
2.4.4 create方式的ref

使用React中的createRef方法,该方法可以返回一个容器,容器中可以存储被ref标识的节点
在这里插入图片描述

2.4.5 create方式的ref

注意:一个容器只能存储一个节点,多次使用该容器则会被覆盖
在这里插入图片描述

2.4.6 事件处理
  1. 通过onXxx属性指定事件处理函数(注意大小写)

    • React使用的是自定义(合成)事件, 而不是使用的原生DOM事件——————————为了更好的兼容性
    • React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)——————————为了更高效
      在这里插入图片描述
  2. 不能过度使用Ref,可以通过event.target得到发生事件的DOM元素对象(就是事件对象)
    什么时候可以避免使用Ref?发生事件的元素正好是我们需要操作的元素在这里插入图片描述

2.5 收集表单数据

2.5.1 非受控组件

使用 ref 来从 DOM 节点中获取表单数据(访问节点,通过节点访问值),与状态没有任何关系,这种就是非受控组件。
在这里插入图片描述

2.5.2 受控组件(建议使用,因为可以防止过度使用Ref)

页面中输入类的DOM,随着输入,将内容维护到状态中,需要用时直接在状态中取出,这就是受控组件
在这里插入图片描述
不用函数柯里化,优化代码
在这里插入图片描述

2.5.3高阶函数

高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数,
常见的高阶函数有:Promise、setTimeout、arr.map()等等

  • 若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
  • 若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。

函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。

  1. 普通JS代码演示函数柯里化
//普通函数
function sum(a,b,c){
    return a+b+c
}

//函数柯里化
function sumK(a){
    return(b)=>{
        return(c)=>{
            return a+b+c
        }
    }
}
const result = sum(1,2,3)
const resultK = sumK(1)(2)(3)
console.log(result,resultK)
  1. 使用高阶函数优化受控组件代码
    在这里插入图片描述

  2. 易错点分析

    • onchange事件回掉如果写了小括号就是将函数的返回值作为onchange的回掉,函数没有返回值就是undefined调用会报错,解决思路就是将返回值写成函数,真正的回掉是返回值里的函数当触发事件时react帮我们调用的,所以将事件对象作为真正回掉函数的形参 在这里插入图片描述

    在这里插入图片描述

    • 这里dataType没有使用方括号,会导致存储状态时是将dataType作为字符串读取的,而不是变量我们传入的参数,对象中所有的属性都是字符串,只不过我们平常简写把引号去掉了,实际上他还是字符串,所以他读取的是dataType字符串而不是这个变量,从而导致状态中多了一个属性dateType,而不是根据我们传入的参数更改的,解决这个问题需要使用[]来读取变量在这里插入图片描述
      在这里插入图片描述
    let a = 'name'
    let obj = {}
    obj.a = '小美' //错误写法 打印的会是{a:'小美'}
    obj[a] = '小美' //正确写法,使用[]读变量, 打印的会是{name:'小美'}
    console.log(obj)
    

2.5 组件的生命周期

2.5.1 引出生命周期

  1. 挂载(mount):当组件第一次被渲染到DOM中,就会为其设置一个计时器,就称为挂载
  2. 卸载(unmount):当DOM中组件被删除时,清除计时器,就成为卸载
  3. ReactDOM.unmountComponentAtNode():卸载组件
  4. componentDidMount:当组件挂载到页面上时只调用一次,调用时机:组件挂载完毕
  5. componentWillUnmount:,调用时机:组件将要卸载时

一个完整生命周期的案例
在这里插入图片描述

<script type="text/babel">
    //创建组件
    class Life extends React.Component{
        state = {
            opacity:1
        }
        //调用时机:组件挂载完毕
        componentDidMount(){
            this.timer = setInterval(()=>{
                let {opacity} = this.state
                opacity -= 0.1
                if(opacity <= 0) opacity = 1
                this.setState({
                    opacity:opacity
                    //opacity 简写:对象的key和value同名可以只写key
                })
            },200)
        }
        //调用时机:组件将要卸载
        componentWillUnmount(){
            //关闭定时器 方式二
            clearInterval(this.timer)
        }
        //调用时机:初始化渲染、状态更新
        render(){
            this.death = () =>{
                //关闭定时器 方式一
                clearInterval(this.timer)
                //卸载组件
                ReactDOM.unmountComponentAtNode(document.getElementById('test'))
            }
            return(
                <div>
                    <h2 style={{opacity:this.state.opacity}}>React学不会怎么办?</h2>
                    <button onClick={this.death}>不活了</button>
                </div>
            )
        }
    }
    //渲染组件
    ReactDOM.render(<Life/>,document.getElementById('test'))
</script>

关键点分析:

  1. 定时器的位置,当想要实现一打开页面自动触发执行一次定时器时,需要将定时器写到componentDidMount函数中,这个函数是与render相同级别的函数,如果不使用这个函数,而是将定时器写在render中会引发无限循环的递归,造成render执行了无数次,这与render的执行次数有关render执行次数是1+n次,n指状态更新的次数,一次执行中在定时器中更新状态,又会重新再执行render,再开启一个定时器,无限循环
    在这里插入图片描述
    在这里插入图片描述
  2. 在卸载组件前需要先关闭定时器,如果没有关闭点击按钮卸组件会报错,原因点击后组件卸载了,但是组件的定时器还一直在跑,并且定时器中还在更改状态,所以导致报错在这里插入图片描述

2.5.2 理解

常见说法:生命周期回调函数、生命周期钩子函数、生命周期函数、生命周期钩子(钩子的含义就是,在合适的时间点把挂载好的这个函数勾出来执行一下)

  1. 组件对象从创建到死亡它会经历特定阶段。
  2. React 组件对象包含一系列勾子函数(生命周期回调函数),在特定的时刻调用,
  3. 我们在定义组件时,在特定的生命周期回调的数,中做持定的工作。

2.5.3 生命周期(旧)

在这里插入图片描述

  1. 挂载(初始化)阶段————由ReactDOM.render()触发,初次渲染
    在这里插入图片描述

构造器constructor ——> 组件将要挂载componentWillMount ——> 渲染render ——> 组件挂载完毕componentDidMount

  1. 卸载组件————由ReactDOM.unmountComponentAtNode()触发
    在这里插入图片描述
    点击卸载按钮卸载组件,卸载之前会调用componentWillUnmount
  2. 更新阶段
    在这里插入图片描述
    • 从setState开始,当状态发生更新,会先调用shouldComponentUpdate,如果返回false,则会中断,不会继续执行调用render重新渲染,不写默认为true
      -
    • 从forceUpdate开始,强制更新,不更改状态也能更新组件,或者shouldComponentUpdate阀门为false时也能进行更新
      在这里插入图片描述
    • 从父组件更新开始,父组件更新了才会执行该流程线,第一次父组件没有更新时不会执行
      在这里插入图片描述

常用钩子
render:初始化渲染或更新渲染调用
componentDidMount:一般在这个钩子中做一些初始化的事,例如:开启定时器,发送网络请求、订阅消息
componentWillUnmountMount:一般在这个钩子中做一些收尾的事,例如关闭定时器、取消订阅消息
reder:必须用的一个

2.5.4 生命周期(新)

在这里插入图片描述

新的生命周期需要更新使用的相关js库
推荐进入BootCDN下载

  1. 废弃的三个生命周期函数
    新版本中,以下三个生命周期函数需要加上UNSAFE_前缀,并不建议使用,因为在异步渲染时误用钩子,可能会出现Bug
    UNSAFE_componentWillMount
    UNSAFE_componentWillUpdate
    UNSAFE_componentWillReceiveProps

     所有will相关的钩子需要加前缀,除了willUnmount
    
  2. 新增了两个生命周期函数(实际使用的频率都比较低)
    statis getDerivedStateFromProps(props, state)
    它应该返回一个对象来更新 state,或者返回 null 就不更新任何内容。
    静态方法,需要使用static修饰。
    该方法接收到两个参数,一个是props(组件的传入的属性如<CustomButton count={10} / >,这里props就是{count:10}),一个是当前组件的state。
    如果定义了静态 getDerivedStateFromProps, React将在调用render之前调用它,无论是在初始挂载时还是在后续更新时。
    罕见用例,即state的值在任何时候都取决于props

    getSnapshotBeforeUpdate(preProps, preState)
    当组件即将要渲染到页面上时,将调用该方法。
    该方法接收到两个参数,一个是旧的props,一个是当前组件旧的 state。
    如使用该方法,则必须要返回一个快照值(snapshot)。可以是任何类型的数据,如数值、字符串、对象等,也可以是null,但不能是undefined。该快照值将作为执行 componentDidUpdate的第三个参数。

  3. 新增的两个钩子演示
    在这里插入图片描述

  4. getSnapshotBeforeUpdate使用场景

class NewsList extends React.Component{
        state = { newsList: [] }
        listRef = React.createRef()
        getSnapshotBeforeUpdate(){
            console.log(this.listRef.current.scrollHeight)
            return this.listRef.current.scrollHeight
        }
        // 解决看指定新闻时,新的新闻进来导致的滚动问题
        componentDidUpdate(preProps,preState,snapshotValue){
            // 方式一
            if(snapshotValue >= 150){
               console.log("scrollTop", this.listRef.current.scrollTop)
               this.listRef.current.scrollTop += 30
             }
             // 方式二
             // this.listRef.current.scrollTop += this.listRef.current.scrollHeight - snapshotValue
        }
        componentDidMount() {
            setInterval(() => {
                const { newsList } = this.state
                const newNewsItem = "新闻" + (newsList.length + 1)
                // 在数组前面加元素,push是在数组后面加元素
                this.setState({ newsList: [newNewsItem, ...newsList] })
            }, 1000)
        }

        render(){
            const { newsList } = this.state
            return(
                <div className="list" ref={this.listRef}>
                    {newsList.map((item,index)=>(
                        <div className="news" key={index}>{item}</div>
                    ))}
                </div>
            )
        }
    }

2.6 DOM的Diffing算法

2.6.1 Diffing算法

在 React 中,DOM 的 Difing(差异比较)算法是一种优化手段,用于确定 虚拟 DOMQ 树与实际 DOM 树之间的差异,并仅更新必要的部分,以提高性能

Diffing算法基本原理
React 在进行 ViualDOM 更新时,会通过 Difing 算法对新l日 Virtual DOM 进行比较,找出变化并仅更新这部分差异,而不是整个重新渲染。这个算法的基本原理可以概括为:

  1. Tree Reconciliation(树协调):对比新旧树的结构,找出变化的部分。
  2. Component Reconciliation(组件协调): 对比同一层级的组件只,找出需要更新的组件

基本原理图
在这里插入图片描述
验证Diffing算法

<script type="text/babel">
        /* 根据页面显示时间一直在更新,input框没有更新来验证diff算法,逐层对比虚拟DOM不同就重新生成新的真实DOM 相同就复用旧的真实DOM,最小的粒度是标签(节点)*/
        class Time extends React.Component{
            state = {data: new Date()}

            componentDidMount(){
                setInterval(()=>{
                    this.setState({
                        data: new Date()
                    })
                },1000)
            }
            render(){
                return(
                    <div>
                        <h1>hello</h1>
                        <input text="text"/>
                        <span>
                            现在是:{this.state.data.toTimeString()}
                            <input text="text"/>
                        </span>
                    </div>
                )
            }
        }
        ReactDOM.render(<Time/>,document.getElementById('test'))
    </script>

2.6.2 Key

  1. Key的作用
    简单的说:key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用。
    详细的说:当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】随后React进行【新虚拟DOM】分【旧虚拟DOM】的diff比较,比较规则如下:

a.旧虚拟DOM中找到了与新虚拟DOM机同的key:
    若虚拟DOM中内容没变,直接使用之前的真实DOM
    若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
b.旧虚拟DOM中未找到与新虚拟DOM相同的key
    根据数据创建新的真实DOM,随后渲染到到页面

  1. 用index作为key可能会引发的问题:

    • 1). 若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新==>界面效果没问题,但效率低。
    • 2). 如果结构中还包含输入类的DOM:会产生错误D0M更新 ==>界面有问题。
    • 3). 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
  2. 开发中如何选择key:

    • 1). 最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值。
    • 2). 如果确定只是简单的展示数据,用index也是可以的。

第3章 React应用

3.1.使用 create-react-app 创建 react 应用

3.1.1 react脚手架

  1. xxx 脚手架: 用来帮助程序员快速创建一个基于 xxx 库的模板项目
    • 包含了所有需要的配置(语法检查、jsx编译、devServer…)←
    • 下载好了所有相关的依赖
    • 可以直接运行一个简单效果
  2. react 提供了一个用于创建react 项目的脚手架库: create-react-app←
  3. 项目的整体技术架构为: react+webpack+es6+eslinte
  4. 使用脚手架开发的项目的特点: 模块化,组件化,工程化

3.1.2 创建项目并启动

windows+R----->输入cmd进入命令控制窗口
第一步,全局安装:npm i -g create-react-app
第二步,切换到想创项目的目录,使用命令:create-react-app react_staging
第三步,进入项目文件夹:cd react_staging
第四步,启动项目:npm start

3.1.3 脚手架项目结构

  • public ---- 静态资源文件夹
    • favicon.icon ------ 网站页签图标
    • index.html -------- 主页面
    • logo192.png ------- logo图
    • logo512.png ------- logo图
    • manifest.json ----- 应用加壳的配置文件
    • robots.txt -------- 爬虫协议文件
  • src ---- 源码文件夹
    • App.css -------- App组件的样式
    • App.js --------- App组件
    • App.test.js ---- 用于给App做测试
    • index.css ------ 样式
    • index.js ------- 入口文件,引入App渲染到页面
    • logo.svg ------- logo图
    • reportWebVitals.js
      — 页面性能分析文件(需要web-vitals库的支持)
    • setupTests.js
      ---- 组件单元测试的文件(需要jest-dom库的支持)

3.1.4 脚手架执行顺序

在这里插入图片描述

3.1.5 小案例

组件可以使用.jsx后缀方便区分,index.js是入口文件
在这里插入图片描述
在这里插入图片描述

3.1.6 样式模块化

当多个css文件中出现名字一样的,会导致样式覆盖问题,所以要使用样式的模块化

在这里插入图片描述

3.1.7 功能界面的组件化编码流程(通用)

  1. 拆分组件: 拆分界面,抽取组件
  2. 实现静态组件: 使用组件实现静态页面效果
  3. 实现动态组件
    • 3.1 动态显示初始化数据
      • 3.1.1 数据类型
      • 3.1.2 数据名称
      • 3.1.2 保存在哪个组件?
    • 3.2 交互(从绑定事件监听开始)

3.2 TodoList案例

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值