React入门到入土系列(一)

73 阅读13分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情

React学习篇

1. React简介

1.1 官网

英文官网:`https://reactjs.org/

中文官网: https://react.docschina.org/

1.2 介绍描述

用于动态构建用户界面JavaScript 库(只关注于视图)

由Facebook开源

1.3 React的特点

  • 声明式编码
  • 组件化编码
  • React Native 编写原生应用
  • 高效,虚拟DOM(优秀的Diffing算法),尽量减少与真实DOM的交互

1.4 React高效的原因

  • 使用虚拟(virtual)DOM, 不总是直接操作页面真实DOM。
  • DOM Diffing算法, 最小化页面重绘

2. React的基本使用

快速安装: create-react-app my-app

1. 相关js库

react.js:React核心库。

react-dom.js:提供操作DOM的react扩展库。

babel.min.js:解析JSX语法代码转为JS代码的库。

2. 创建虚拟DOM的两种方式

  • 纯JS方式(一般不用)
  • JSX方式

1.纯JS方式(一般不用)

    <!-- 准备好一个“容器” -->
    <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><script type="text/javascript" > 
        //1.创建虚拟DOM
        const VDOM = React.createElement('h1',{id:'title'},React.createElement('span',{},'Hello,React'))
        //2.渲染虚拟DOM到页面
        ReactDOM.render(VDOM,document.getElementById('test'))
    </script>

2.JSX方式

    <!-- 准备好一个容器 -->
    <a class="test"></a>
​
    <!-- 注意引入顺序 -->
    <!-- 引入react核心库 -->
    <script src="/js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script src="/js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转为js -->
    <script src="/js/babel.min.js"></script>
​
    <script type="text/babel">  //此处一定要写babel
        // 1.创建虚拟DOM
        const VDOM = (  //此处不加引号,不是字符串
            <h1 id='text'><span>hello,react</span></h1>
        )
        // 2.渲染虚拟DOM到页面
        ReactDOM.render(VDOM, document.querySelector('.test'))
​
    </script>

总结一下:使用纯js方式创建虚拟DOM过于繁琐,推荐使用JSX语法糖。

3. 虚拟DOM与真实DOM

虚拟DOM本质是Object类型的对象(一般对象)

  • 虚拟DOM对象最终都会被React转换为真实的DOM
  • 我们编码时基本只需要操作react的虚拟DOM相关数据, react会转换为真实DOM变化而更新界。
  • 虚拟DOM比较轻,真实DOM比较重,呈现在页面上

3. React JSX

1. JSX

  • 全称: JavaScript XML
  • react定义的一种类似于XML的JS扩展语法: JS + XML本质是React.createElement(component, props, ...children)方法的语法糖
  • 作用: 用来简化创建虚拟DOM
  • jsx语法规则:
  1. 定义虚拟DOM时,不要写引号
  2. 标签中混入JS表达式要用{}。
  3. 样式的类名指定不要用class,要用className。
  4. 内联样式,要用style={{key:value}} 的形式去写。
  5. 只有一个根标签
  6. 标签必须闭合
  7. 标签首字母 (1).若小写字母开头,则将该标签转为html中同名元素,若html中无该标签对应的同名元素,则报错。 (2).若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。

例如:

    <script type="text/babel" >
        const myId = 'aTgUiGu'
        const myData = 'HeLlo,rEaCt'//1.创建虚拟DOM
        const VDOM = (
            <div>
                <h2 className="title" id={myId.toLowerCase()}>
                    <span style={{color:'white',fontSize:'29px'}}>{myData.toLowerCase()}</span>
                </h2>
                <h2 className="title" id={myId.toUpperCase()}>
                    <span style={{color:'white',fontSize:'29px'}}>{myData.toLowerCase()}</span>
                </h2>
                <input type="text"/>
            </div>
        )
        //2.渲染虚拟DOM到页面
        ReactDOM.render(VDOM,document.getElementById('test'))
    </script>

一定注意区分:【js语句(代码)】与【js表达式】 1.表达式:一个表达式会返回一个值,可以放在任何一个需要值的地方 下面这些都是表达式: (1). a (2). a+b (3). demo(1) (4). arr.map() (5). function test () {} 2.语句(代码):没有可以返回的值 下面这些都是语句(代码): (1).if(){} (2).for(){} (3).switch(){case:xxxx}

2. 渲染虚拟DOM

语法: ReactDOM.render(VDOM,document.getElementById('test'))

作用: 将虚拟DOM元素渲染到页面中的真实容器DOM中显示

参数说明

  1. 参数一: 纯js或jsx创建的虚拟dom对象

  2. 参数二: 用来包含虚拟DOM元素的真实dom元素对象(一般是一个div)

3. JSX练习

需求: 动态展示如下列表

image-20220506235138333.png

image-20220506235218487.png

注意:遍历每个标签需要唯一的key: 但遍历列表时,key最好不要用index image-20220506235224673.png

推荐:key用id

image-20220510205053473.png

4. 模块与组件的理解

1. 模块

理解:向外提供特定功能的js程序, 一般就是一个js文件

为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂。

作用:复用js, 简化js的编写, 提高js运行效率

2. 组件

理解:用来实现 局部功能 效果代码和资源的集合(html/css/js/image等等)

为什么要用组件: 一个界面的功能更复杂

作用:复用编码, 简化项目编码, 提高运行效率

3. 模块化

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

4. 组件化

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

第二章、React面向组件编程

使用React开发者工具调试:

安装:React Developer Tools谷歌插件

1. 组件的使用

函数式组件:

<script type="text/babel">
//1.创建函数式组件
function MyComponent() {
    console.log(this); //此处的this是undefined,因为babel编译后开启了严格模式
    return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
}
//2.渲染组件到页面
ReactDOM.render(<MyComponent />, document.querySelector('#test'))
/*
执行了ReactDOM.render(<MyComponent/>.......之后,发生了什么?
    1.React解析组件标签,找到了MyComponent组件。
    2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。
*/
</script>

注意:

  1. 组件必须首字母大写,如:MyComponent
  2. 此处的this是undefined,因为babel编译后开启了严格模式
  3. 渲染时组件以标签形式:<MyComponent />且必须闭合

类式组件:

<script type="text/babel">
    //1.创建类式组件
    class MyComponent extends React.Component {
        render() {
            //render是放在哪里的?—— MyComponent的原型对象上,供实例使用。
            //render中的this是谁?—— MyComponent的实例对象 <=> MyComponent组件实例对象。
            console.log('render中的this:', this);
            return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
        }
    }
    //2.渲染组件到页面
    ReactDOM.render(<MyComponent />, document.getElementById('test'))
    /*
    执行了ReactDOM.render(<MyComponent/>.......之后,发生了什么?
        1.React解析组件标签,找到了MyComponent组件。
        2.发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。
        3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。
    */
</script>

注意:render是放在—— MyComponent的原型对象上,供实例使用。

image-20220510210254647.png

2. state

组件三大核心属性之一:state

1. 理解

1.state是组件对象最重要的属性, 值是对象 ( 可以包含多个key-value的组合)

2.组件被称为 "状态机" , 通过更新组件的state更新对应的页面显示(重新渲染组件)

2. 强烈注意

  1. 组件中render方法中的this为组件实例对象

  2. 组件自定义的方法this为undefined,如何解决?

    a) 强制绑定this: 通过函数对象的bind()

    b) 箭头函数

  3. state数据,不能直接修改或更新

3. state的标准方式

总结:

1.解决 changeWeather 中 this指向问题 this.changeWeather = this.changeWeather.bind(this)

2.状态必须通过setState进行更新,且更新是一种合并,不是替换。

例:this.setState({isHot:!isHot})

3.状态(state)不可直接更改,下面这行就是直接更改!!!

例:this.state.isHot = !isHot

4.this.changeWeather() 这里一定不能写小括号,否则是直接调用,所以不是通过实例调用的 ,this为undefined, 方法里也开了严格模式

5.直接调用(传的是函数的返回值:this.changeWeather=undefined),实例回调(传的是整个函数:this.changeWeather)

<script type="text/babel">
    //1.创建组件
    class Weather extends React.Component{
        
        //构造器调用几次? ———— 1次
        constructor(props){
            console.log('constructor');
            super(props)
            //初始化状态
            this.state = {isHot:false,wind:'微风'}
            //解决changeWeather中this指向问题
            this.changeWeather = this.changeWeather.bind(this)
        }
​
        //render调用几次? ———— 1+n次 1是初始化的那次 n是状态更新的次数
        render(){
            console.log('render');
            //读取状态
            const {isHot,wind} = this.state
            return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
            // 这里不能用{demo()}
            // onClick不能写成onclick
            // this.changeWeather()这里一定不能写小括号,否则是直接调用
        }
​
        //changeWeather调用几次? ———— 点几次调几次
        changeWeather(){
            //changeWeather放在哪里? ———— Weather的原型对象上,供实例使用
            //changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用,this为undefined
            //类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined
            
            console.log('changeWeather');
            //获取原来的isHot值
            const isHot = this.state.isHot
            //严重注意:状态必须通过setState进行更新,且更新是一种合并,不是替换。
            this.setState({isHot:!isHot})
            console.log(this);
​
            //严重注意:状态(state)不可直接更改,下面这行就是直接更改!!!
            //this.state.isHot = !isHot //这是错误的写法
        }
    }
    //2.渲染组件到页面
    ReactDOM.render(<Weather/>,document.getElementById('test'))
            
</script>

4. state的简写方式

<script type="text/babel">
    //1.创建组件
    class Weather extends React.Component{
        //初始化状态
        state = {isHot:false,wind:'微风'}
​
        render(){
            // 解构赋值
            const {isHot,wind} = this.state
            return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
        }
​
        //2.自定义方法————要用赋值语句的形式+箭头函数(箭头没有自己的this)
        changeWeather = ()=>{
            const isHot = this.state.isHot
            this.setState({isHot:!isHot})
        }
    }
    //3.渲染组件到页面
    ReactDOM.render(<Weather/>,document.getElementById('test'))
            
</script>

3. props

组件三大核心属性之一:props

1. 理解

每个组件对象都会有props(properties的简写)属性

组件标签 的 所有属性 都保存在props中

2. 作用

通过标签属性从组件外向组件内传递变化的数据

注意: 组件内部不要修改props数据

3. 总结:

3.1 内部读取某个属性值

this.props.name

3.2 对props中的属性值进行类型限制和必要性限制

第一种方式(React v15.5 开始已弃用)

Person.propTypes = {
 name: React.PropTypes.string.isRequired,
 age: React.PropTypes.number
}

第二种方式(新) :使用prop-types库进限制 (需要引入prop-types库)

Person.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number. 
}

3.3 扩展属性: 将对象的所有属性通过props传递

<Person {...person}/>

3.4 默认属性值:

Person.defaultProps = {
  age: 18,
  sex:'男'
}

3.5 组件类的构造函数

constructor(props){
  super(props)
  console.log(props)//打印所有属性
}

4. 基本使用

<script type="text/babel">
	//1. 创建组件
	class Person extends React.Component{
		render(){
			// console.log(this);
			const {name,age,sex} = this.props
			return (
				<ul>
					<li>姓名:{name}</li>
					<li>性别:{sex}</li>
					<li>年龄:{age+1}</li>
				</ul>
			)
		}
	}
	//2. 渲染组件到页面
	ReactDOM.render(<Person name="jerry" age={19}  sex="男"/>,document.getElementById('test1'))
	ReactDOM.render(<Person name="tom" age={18} sex="女"/>,document.getElementById('test2'))

	const p = {name:'老刘',age:18,sex:'女'}
	// console.log('@',...p);
	// ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>,document.getElementById('test3'))
	ReactDOM.render(<Person {...p}/>,document.getElementById('test3'))
</script>

5.对props进行限制

<script type="text/babel">
	//创建组件
	class Person extends React.Component {
		render() {
			// console.log(this);
			const { name, age, sex } = this.props
			//注意:props是只读的
			//this.props.name = 'jack' //此行代码会报错,因为props是只读的
			return (
				<ul>
					<li>姓名:{name}</li>
					<li>性别:{sex}</li>
					<li>年龄:{age + 1}</li>
				</ul>
			)
		}
	}
	//对标签属性进行类型、必要性的限制
	// 此处propTypes 首字母为小写
	Person.propTypes = {
		name: PropTypes.string.isRequired, //限制name必传,且为字符串
		sex: PropTypes.string,//限制sex为字符串
		age: PropTypes.number,//限制age为数值
		speak: PropTypes.func,//限制speak为函数
		// 注意:string,number,func 小写
	}
	//指定默认标签属性值
	Person.defaultProps = {
		sex: '男',//sex默认值为男
		age: 18 //age默认值为18
	}
	//渲染组件到页面
	ReactDOM.render(<Person name={100} speak={speak} />, document.getElementById('test1'))
	ReactDOM.render(<Person name="tom" age={18} sex="女" />, document.getElementById('test2'))

	const p = { name: '老刘', age: 18, sex: '女' }
	// console.log('@',...p);
	// ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>,document.getElementById('test3'))
	ReactDOM.render(<Person {...p} />, document.getElementById('test3'))

	function speak() {
		console.log('我说话了');
	}
</script>

6.props的简写方式

	<script type="text/babel">
		//1. 创建组件
		class Person extends React.Component {
			// 构造器能省略则省
			// constructor(props) {
			// 	//构造器是否接收props,是否传递给super,取决于:是否希望在构造器中通过this访问props
			// 	// console.log(props);
			// 	super(props)
			// 	console.log('constructor', this.props);
			// }

			//2. 对标签属性进行类型、必要性的限制
			// static 给Person类添加属性,若不加则是给Person的实例添加
			static propTypes = {
				name: PropTypes.string.isRequired, //限制name必传,且为字符串
				sex: PropTypes.string,//限制sex为字符串
				age: PropTypes.number,//限制age为数值
			}

			//指定默认标签属性值
			static defaultProps = {
				sex: '男',//sex默认值为男
				age: 18 //age默认值为18
			}
			
			render() {
				console.log(this);
				const { name, age, sex } = this.props
				//props是只读的
				//this.props.name = 'jack' //此行代码会报错,因为props是只读的
				return (
					<ul>
						<li>姓名:{name}</li>
						<li>性别:{sex}</li>
						<li>年龄:{age + 1}</li>
					</ul>
				)
			}
		}

		//3. 渲染组件到页面
		ReactDOM.render(<Person name="jerry" />, document.getElementById('test1'))
	</script>

7.函数组件使用props

	<script type="text/babel">
		//1. 创建组件
		function Person (props){
			const {name,age,sex} = props
			return (
					<ul>
						<li>姓名:{name}</li>
						<li>性别:{sex}</li>
						<li>年龄:{age}</li>
					</ul>
				)
		}
        //2. 对标签属性进行类型、必要性的限制
		Person.propTypes = {
			name:PropTypes.string.isRequired, //限制name必传,且为字符串
			sex:PropTypes.string,//限制sex为字符串
			age:PropTypes.number,//限制age为数值
		}

		//3. 指定默认标签属性值
		Person.defaultProps = {
			sex:'男',//sex默认值为男
			age:18 //age默认值为18
		}
		//4. 渲染组件到页面
		ReactDOM.render(<Person name="jerry"/>,document.getElementById('test1'))
	</script>

4. refs

组件三大核心属性之一:refs与事件处理

1. 理解

组件内的标签可以定义ref属性标识自己类似于id

2. 字符串形式的ref(已弃用)

<input ref="input1"/>

image-20220508203949134.png

<script type="text/babel">
	//创建组件
	class Demo extends React.Component {
		//展示左侧输入框的数据
		showData = () => {
			const { input1 } = this.refs
			console.log(this);
			alert(input1.value)
		}
		//展示右侧输入框的数据
		showData2 = () => {
			const { input2 } = this.refs
			alert(input2.value)
		}
		render() {
			return (
				<div>
					<input ref="input1" type="text" placeholder="点击按钮提示数据" />&nbsp;
					<button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
					<input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />
				</div>
			)
		}
	}
	//渲染组件到页面
	ReactDOM.render(<Demo a="1" b="2" />, document.getElementById('test'))
</script>

3. 回调形式的ref(常用)

<input ref={(c)=>{this.input1 = c}}

image-20220508203848695.png

<script type="text/babel">
	//创建组件
	class Demo extends React.Component {
		//展示左侧输入框的数据
		showData = () => {
			const { input1 } = this
			alert(input1.value)
		}
		//展示右侧输入框的数据
		showData2 = () => {
			const { input2 } = this
			alert(input2.value)
		}
		render() {
			return (
				<div>
					<input ref={c => this.input1 = c} type="text" placeholder="点击按钮提示数据" />&nbsp;
					<button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
					<input onBlur={this.showData2} ref={c => this.input2 = c} type="text" placeholder="失去焦点提示数据" />&nbsp;
				</div>
			)
		}
	}
	//渲染组件到页面
	ReactDOM.render(<Demo a="1" b="2" />, document.getElementById('test'))
</script>

4. createRef创建ref容器·(最推荐)

专人专用:一个myRef中只能放一个标签

image-20220508204953097.png

<script type="text/babel">
	//创建组件
	class Demo extends React.Component{
		/* 
			React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是“专人专用”的
			*/
		myRef = React.createRef()
		myRef2 = React.createRef()
		//展示左侧输入框的数据
		showData = ()=>{
			alert(this.myRef.current.value);
		}
		//展示右侧输入框的数据
		showData2 = ()=>{
			alert(this.myRef2.current.value);
		}
		render(){
			return(
				<div>
					<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>&nbsp;
					<button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
					<input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦点提示数据"/>&nbsp;
				</div>
			)
		}
	}
	//渲染组件到页面
	ReactDOM.render(<Demo a="1" b="2"/>,document.getElementById('test'))
</script>

小细节:关于回调 refs 的说明 :

如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次第一次传入参数 null,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。

<input ref={c => { this.input1 = c; console.log('@:', c); }} type="text" placeholder="点击按钮提示" /><br />

image-20220508201517911.png

解决:通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的

// 定义函数 ,用于回调
setInput = (c) => {
    this.input1 = c
    console.log('@:', c)
}

render() {
    return (
        <div>
            <input ref={this.setInput} type="text" placeholder="点击按钮提示" /><br />
        </div>
    )
}

如下:只被调用了一次

image-20220508202629255.png

5. 事件处理

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

    1. React使用的是自定义(合成)事件, 而不是使用的原生DOM事件—为了更好的兼容性

    2. React中的事件通过 事件委托 方式处理的(委托给组件最外层的元素)—为了高效

  • 通过 event.target 得到 发生事件DOM元素对象不要过度的使用ref 发生事件的元素,可以省略refs

如:

showDate = (event) => {
    console.log(event.target.value);
}
 <input onBlur={this.showDate} type="text" placeholder="点击按钮提示" /><br />

5. 收集表单数据

1. 受控组件

理解:随着输入的值,存入state中,再取出来(推荐)

优势:省略掉了ref

onChange表单的value值有变化,则调用

<form onSubmit={this.handleSubmit}>
    name: <input onChange={this.saveUsername} type="text" name="username" />
    password: <input onChange={this.savePassword} type="password" name="password" />
    <button>登录</button>
</form>

image-20220509002508072.png

例子:

<script type="text/babel">
	//创建组件
	class Login extends React.Component{

		//初始化状态
		state = {
			username:'', //用户名
			password:'' //密码
		}

		//保存用户名到状态中
		saveUsername = (event)=>{
			this.setState({username:event.target.value})
		}

		//保存密码到状态中
		savePassword = (event)=>{
			this.setState({password:event.target.value})
		}

		//表单提交的回调
		handleSubmit = (event)=>{
			event.preventDefault() //阻止表单提交
			const {username,password} = this.state
			alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
		}

		render(){
			return(
				<form onSubmit={this.handleSubmit}>
					用户名:<input onChange={this.saveUsername} type="text" name="username"/>
					密码:<input onChange={this.savePassword} type="password" name="password"/>
					<button>登录</button>
				</form>
			)
		}
	}
	//渲染组件
	ReactDOM.render(<Login/>,document.getElementById('test'))
</script>

优化:

以下两种方式都常用:

1.用到高阶函数+柯里化

image-20220509093457277.png

<script type="text/babel">
	class Login extends React.Component{
		//初始化状态
		state = {
			username:'', //用户名
			password:'' //密码
		}

		//保存表单数据到状态中
		//saveFormData 为高阶函数,并用到了柯里化
		saveFormData = (dataType)=>{
			return (event)=>{
                // this.setState({ dataType: event.target.value }) // state里: dataType:"12"
				this.setState({[dataType]:event.target.value}) //state里:username:"12" 注意这里的[]
			}
		}

		//表单提交的回调
		handleSubmit = (event)=>{
			event.preventDefault() //阻止表单提交
			const {username,password} = this.state
			alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
		}
		render(){
			return(
				<form onSubmit={this.handleSubmit}>
					用户名:<input onChange={this.saveFormData('username')} type="text" name="username"/>
					密码:<input onChange={this.saveFormData('password')} type="password" name="password"/>
					<button>登录</button>
				</form>
			)
		}
	}
	//渲染组件
	ReactDOM.render(<Login/>,document.getElementById('test'))
</script>

2.不用柯里化实现:

//保存表单数据到状态中
saveFormData = (dataType,event)=>{
	this.setState({[dataType]:event.target.value})
}
<form onSubmit={this.handleSubmit}>
	用户名:<input onChange={event => this.saveFormData('username',event) } type="text" name="username"/>
	密码:<input onChange={event => this.saveFormData('password',event) } type="password" name="password"/>
	<button>登录</button>
</form>

2. 非受控组件

理解:现用现取

<form onSubmit={this.handleSubmit}>
    name: <input ref={c => this.username = c} type="text" name="username" />
    password: <input ref={c => this.password = c} type="password" name="password" />
    <button>登录</button>
</form>

例子:

<script type="text/babel">
	//创建组件
	class Login extends React.Component{
		handleSubmit = (event)=>{
			event.preventDefault() //阻止表单提交
			const {username,password} = this
			alert(`你输入的用户名是:${username.value},你输入的密码是:${password.value}`)
		}
		render(){
			return(
				<form onSubmit={this.handleSubmit}>
					用户名:<input ref={c => this.username = c} type="text" name="username"/>
					密码:<input ref={c => this.password = c} type="password" name="password"/>
					<button>登录</button>
				</form>
			)
		}
	}
	//渲染组件
	ReactDOM.render(<Login/>,document.getElementById('test'))
</script>

6. 组件的生命周期

理解

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

又可以叫作:生命周期回调函数 <=> 生命周期钩子函数 <=> 生命周期函数 <=> 生命周期钩子

1. 生命周期流程图(旧)

image-20220510090920917.png 生命周期的三个阶段(旧)

初始化阶段: 由ReactDOM.render()触发---初次渲染

  1. constructor()

  2. componentWillMount()

  3. render()

  4. componentDidMount() =====> 常用

    一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息

image-20220510082708744.png 更新阶段:组件内部this.setSate()父组件重新render触发

  1. shouldComponentUpdate()
  2. componentWillUpdate()
  3. render() =====> 必须使用的一个
  4. componentDidUpdate()

卸载组件: 由ReactDOM.unmountComponentAtNode()触发

  1. componentWillUnmount() =====> 常用

一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息

练习代码:

效果图:

1.展示,修改状态拆卸组件跟新状态和强制跟新状态中的生命周期

image-20220510091919266.png

image-20220510090319538.png 2.展示更改父组件A的状态,来修改B的状态

image-20220510091612466.png

<script type="text/babel">
class Count extends React.Component {

	//构造器
	constructor(props) {
		console.log('Count---constructor');
		super(props)
		//初始化状态
		this.state = { count: 0 }
	}

	//加1按钮的回调
	add = () => {
		//获取原状态
		const { count } = this.state
		//更新状态
		this.setState({ count: count + 1 })
	}

	//卸载组件按钮的回调
	death = () => {
		ReactDOM.unmountComponentAtNode(document.getElementById('test'))
	}

	//强制更新按钮的回调
	force = () => {
		this.forceUpdate()
	}

	//组件将要挂载的钩子
	componentWillMount() {
		console.log('Count---componentWillMount');
	}

	//组件挂载完毕的钩子
	componentDidMount() {
		console.log('Count---componentDidMount');
	}

	//组件将要卸载的钩子
	componentWillUnmount() {
		console.log('Count---componentWillUnmount');
	}

	//控制组件更新的“阀门” 
	// 若不写,默认返回值为true,开启阀门
	// 若写了,不给返回值,则不执行后面的生命周期
	shouldComponentUpdate() {
		console.log('Count---shouldComponentUpdate');
		return true
	}

	//组件将要更新的钩子
	// 强制跟新
	componentWillUpdate() {
		console.log('Count---componentWillUpdate');
	}

	//组件更新完毕的钩子
	componentDidUpdate() {
		console.log('Count---componentDidUpdate');
	}

	render() {
		console.log('Count---render');
		const { count } = this.state
		return (
			<div>
				<h2>当前求和为:{count}</h2>
				<button onClick={this.add}>点我+1</button>
				<button onClick={this.death}>卸载组件</button>
				<button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button>
			</div>
		)
	}
}

//渲染组件
ReactDOM.render(<Count />, document.getElementById('test'))
</script>
<script type="text/babel">
//父组件A
class A extends React.Component {
	//初始化状态
	state = { carName: '奔驰' }

	changeCar = () => {
		this.setState({ carName: '奥拓' })
	}

	render() {
		return (
			<div>
				<div>我是A组件</div>
				<button onClick={this.changeCar}>换车</button>
				{/*注意:用props方法给B组件添加属性carName*/}
				<B carName={this.state.carName} />
			</div>
		)
	}
}

//子组件B
class B extends React.Component {
	//组件将要接收新的props的钩子
	// 小坑:第一次接受的props不算
	componentWillReceiveProps(props) {
		console.log('B---componentWillReceiveProps', props);
	}

	//控制组件更新的“阀门”
	shouldComponentUpdate() {
		console.log('B---shouldComponentUpdate');
		return true
	}
	//组件将要更新的钩子
	componentWillUpdate() {
		console.log('B---componentWillUpdate');
	}

	//组件更新完毕的钩子
	componentDidUpdate() {
		console.log('B---componentDidUpdate');
	}

	render() {
		console.log('B---render');
		return (
			<div>我是B组件,接收到的车是:{this.props.carName}</div>
		)
	}
}

//渲染组件
ReactDOM.render(<A />, document.getElementById('test'))
</script>

注意: 用props方法给B组件添加属性:<B carName={this.state.carName} />

image-20220510084504507.png

2. 生命周期的三个阶段(新)

image-20220510154727420.png

1. 初始化阶段: 由ReactDOM.render()触发---初次渲染

  • constructor()

  • getDerivedStateFromProps 不常用

    Derived:派生

  • render()

  • componentDidMount()

2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发

  • getDerivedStateFromProps

  • shouldComponentUpdate()

  • render()

  • getSnapshotBeforeUpdate 不常用

    Snapshot:快照

  • componentDidUpdate()

3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发

  • componentWillUnmount()

总结: 带will的前面都要加UNFSAFE_前缀,如:unsafe_componentwillupdate),除componentWillUnmount()之外

若state的值在任何时候都取决于props,那么可以使用getDerivedStateFromProps

3. 重要的勾子

  1. render:初始化渲染或更新渲染调用
  2. componentDidMount:开启监听, 发送ajax请求
  3. componentWillUnmount:做一些收尾工作, 如: 清理定时器

4. 即将废弃的勾子

  1. componentWillMount
  2. componentWillReceiveProps
  3. componentWillUpdate

现在使用会出现警告,下一个大版本需要加上UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用。

拓展: getSnapshotBeforeUpdate的使用场景:

描述:不断跟新新闻,但页面停留不动

image-20220510164329662.png

	<script type="text/babel">
		class NewsList extends React.Component{

			state = {newsArr:[]}

			componentDidMount(){
				setInterval(() => {
					//获取原状态
					const {newsArr} = this.state
					//模拟一条新闻
					const news = '新闻'+ (newsArr.length+1)
					//更新状态
					this.setState({newsArr:[news,...newsArr]})
				}, 1000);
			}

			getSnapshotBeforeUpdate(){
				return this.refs.list.scrollHeight
			}

			componentDidUpdate(preProps,preState,height){
				this.refs.list.scrollTop += this.refs.list.scrollHeight - height
			}

			render(){
				return(
					<div className="list" ref="list">
						{
							this.state.newsArr.map((n,index)=>{
								return <div key={index} className="news">{n}</div>
							})
						}
					</div>
				)
			}
		}
		ReactDOM.render(<NewsList/>,document.getElementById('test'))
	</script>

5. Diffing算法案例:

描述:将state写入时间,开启定时器不断渲染时间到页面,每次都会进行diffing匹配,Diffing会逐层匹配最小力度为节点,修改输入框不会改变页面效果。

image-20220510190957845.png

	<script type="text/babel">
		class Time extends React.Component {
			state = {date: new Date()}

			componentDidMount () {
				setInterval(() => {
					this.setState({
						date: new Date()
					})
				}, 1000)
			}

			render () {
				return (
					<div>
						<h1>hello</h1>
						<input type="text"/>
						<span>
							现在是:{this.state.date.toTimeString()}
							<input type="text"/>
						</span>
					</div>
				)
			}
		}

		ReactDOM.render(<Time/>,document.getElementById('test'))
</script>

7. key的作用演示:

描述:分别用index,和id作为key来渲染页面,然后点击增加小王,效果如图:

image-20220510194719025.png

<script type="text/babel">

class Person extends React.Component{

	state = {
		persons:[
			{id:1,name:'小张',age:18},
			{id:2,name:'小李',age:19},
		]
	}

	add = ()=>{
		const {persons} = this.state
		const p = {id:persons.length+1,name:'小王',age:20}
		this.setState({persons:[p,...persons]})
	}

	render(){
		return (
			<div>
				<h2>展示人员信息</h2>
				<button onClick={this.add}>添加一个小王</button>
				<h3>使用index(索引值)作为key</h3>
				<ul>
					{
						this.state.persons.map((personObj,index)=>{
							return <li key={index}>{personObj.name}---{personObj.age}<input type="text"/></li>
						})
					}
				</ul>
				<hr/>
				<hr/>
                    
				<h3>使用id(数据的唯一标识)作为key</h3>
				<ul>
					{
						this.state.persons.map((personObj)=>{
							return <li key={personObj.id}>{personObj.name}---{personObj.age}<input type="text"/></li>
						})
					}
				</ul>
			</div>
		)
	}
}

ReactDOM.render(<Person/>,document.getElementById('test'))
</script>

注意: this.setState({persons:[p,...persons]}):将p放在,persons数组里第一条

6. 经典面试题:

1). react/vue中的key有什么作用?(key的内部原理是什么?)

2). 为什么遍历列表时,key最好不要用index?

3).开发中如何选择key?

1. 虚拟DOM中key的作用:

1). 简单的说: key是虚拟DOM对象标识, 在更新显示时key起着极其重要的作用。

2). 详细的说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】, 随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:

  • 旧虚拟DOM中找到了与新虚拟DOM 相同的key

(1).若虚拟DOM中内容没变, 直接使用之前的真实DOM

(2).若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM

  • 旧虚拟DOM中未找到与新虚拟DOM相同的key

根据数据创建新的真实DOM,随后渲染到到页面

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

  • 若对数据进行:逆序添加、逆序删除破坏顺序操作:

    会产生没有必要真实DOM更新 ==> 界面效果没问题, 但效率低。

  • 如果结构中还包含输入类的DOM:

    会产生错误DOM更新 ==> 界面有问题。

注意: 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,

仅用于渲染列表用于展示,使用index作为key是没有问题的。

3. 开发中如何选择key?

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

key使用index和使用id的区别:

/* 
	慢动作回放----使用index索引值作为key

		初始数据:
				{id:1,name:'小张',age:18},
				{id:2,name:'小李',age:19},
		初始的虚拟DOM:
				<li key=0>小张---18<input type="text"/></li>
				<li key=1>小李---19<input type="text"/></li>

		更新后的数据:
				{id:3,name:'小王',age:20},
				{id:1,name:'小张',age:18},
				{id:2,name:'小李',age:19},
		更新数据后的虚拟DOM:
				<li key=0>小王---20<input type="text"/></li>
				<li key=1>小张---18<input type="text"/></li>
				<li key=2>小李---19<input type="text"/></li>

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

慢动作回放----使用id唯一标识作为key

		初始数据:
				{id:1,name:'小张',age:18},
				{id:2,name:'小李',age:19},
		初始的虚拟DOM:
				<li key=1>小张---18<input type="text"/></li>
				<li key=2>小李---19<input type="text"/></li>

		更新后的数据:
				{id:3,name:'小王',age:20},
				{id:1,name:'小张',age:18},
				{id:2,name:'小李',age:19},
		更新数据后的虚拟DOM:
				<li key=3>小王---20<input type="text"/></li>
				<li key=1>小张---18<input type="text"/></li>
				<li key=2>小李---19<input type="text"/></li>


	*/

复习:

1. 类的基本知识

总结:

1.类中的构造器不是必须要写的,要对实例进行一些初始化的操作,如添加指定属性时才写

2.如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super是必须要调用的

3.类中所定义的方法,都放在了类的原型对象上供实例去使用

2.bind的用法

bind()⽅法主要就是将函数绑定到某个对象, bind()会创建⼀个函数,函数体内的this对象的值会被绑定到传⼊bind()第⼀个参数的值

<script>
    function demo() {
        console.log(this);
    }
    demo() //this指向window
    const w = demo.bind({ a: 1 })
    w() //this指向 { a: 1 }对象
</script>

3.对象的相关知识

  • 在对象里面,读取变量必须用 []
let a = 'name'
let obj = {} 

obj.a = "tom" // {a: 'tom'}
obj[a] = 'tom' // {name: 'tom'}
  • {opacity:opacity} 对象的简写模式 {opacity}
  • 返回对象简洁方式

image-20220515172716457.png

4.函数的柯里化

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

/* function sum(a,b,c){
	return a+b+c // 6
} */

function sum(a){
	return(b)=>{
		return (c)=>{
			return a+b+c
		}
	}
}
const result = sum(1)(2)(3) 
console.log(result); // 6

5.高级函数

如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。

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

常见的高阶函数有:Promise、setTimeout、arr.map()等等

//saveFormData 为高阶函数 符合第二种 ,并用到了柯里化
saveFormData = (dataType)=>{
	return (event)=>{
		this.setState({[dataType]:event.target.value})
	}
}

一般用于直接调用:

<li style={{}} onMouseEnter={this.handleMouse(true)} onMouseLeave={this.handleMouse(false)}>
handleMouse = (flag)=>{
    return()=>{
        this.setState({mouse:false})
    }
}