React 组件生命周期与三大属性state、refs、props(二)

React 组件生命周期与三大属性state、refs、props(二)

React 官网链接:

模块与组件介绍

模块 与 模块化

所谓 模块 指的是向外提供特定功能的JS程序( 一般就是一个JS文件)

随着业务逻辑增加,代码越来越多且复杂,所以要通过 模块 来复用JS, 简化JS的编写, 提高JS运行效率

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

组件 与 组件化

所谓 组件 指的是用来实现局部功能效果的代码和资源的集合(html/css/js/image等等)

随着业务逻辑增加, 一个界面的功能越来越复杂,可以使用组件来复用编码, 简化项目, 提高运行效率

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

example:

请添加图片描述

React 面向组件编程

为方便查看网站的组件,可以安装相应的开发者工具:React开发者工具 react developer tools 安装

组件介绍

React组件有两种:

  • 类组件,定义一个类
  • 函数组件,定义一个函数

在后续的开发中,其实使用比较多的是类组件,下面也会详细说明类组件中的三大属性,在react中占用很重要的地位

函数组件

React在渲染函数组件时,解析组件标签,找到相应的函数组件。发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中

example:

<script type="text/babel">
		//1.创建函数式组件
		function MyComponent(){
			console.log(this); //此处的this是undefined,因为babel编译后开启了严格模式
			return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
		}
		//2.渲染组件到页面 <div id = "test" />
		ReactDOM.render(<MyComponent/>,document.getElementById('test'))
</script>
类组件

React在渲染类组件时,首先解析组件标签,找到相应的类组件;然后再发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。

example:

<script type="text/babel">
	//1.创建类式组件(固定写法,继承 React.Component)
	class MyComponent extends React.Component {
		render(){
			//render是放在哪里的?—— MyComponent的原型对象上,供实例使用。
			//render中的this是谁?—— MyComponent的实例对象 <=> MyComponent组件实例对象。
			console.log('render中的this:',this);
			return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
		}
	}
	//2.渲染组件到页面 <div id = "test" />
	ReactDOM.render(<MyComponent/>,document.getElementById('test'))
</script>

log:

请添加图片描述

注意点:

  • 组件名必须首字母大写
  • 虚拟DOM元素只能有一个根元素
  • 虚拟DOM元素必须有结束标签

类组件三大属性介绍

结合上述讲解的React渲染类组件的流程:

这里我的理解是面向组件编程类似于面向对象编程,我们首先定义了一个类组件,在React渲染的时候,根据定义的原型对象实例化出一个实例对象,既然 new 出了实例,实例肯定有相应的属性,在上面我们把 this 输出在控制台上,看到输出,的确是一个类组件实例:

请添加图片描述

其中很重要的三个属性是React中很重要的东西:

  • state
  • refs
  • props

下面逐一来介绍这三个属性

state

state是组件对象最重要的属性,值是对象 (可以包含多个key-value的组合) ,通过更新组件的state来更新对应的页面显示(重新渲染组件)

组件被称为"状态机",在第一次渲染的时候,可以定义组件初始化状态,如果涉及到组件的更新,我们就需要借助state来更新,通过调用 setState() 方法来更新state,React会重新调用 render() 方法来更新页面的显示

State的初始与更新:

  • 初始化:state 的初始化有两种方式

    • 原型对象构造函数初始化

      constructor(props){
           super(props)
           //初始化状态,这里的this就是实例化对象
           // 定义两个key,分别为isHot与wind
           this.state = {isHot:false,wind:'微风'}
      }
      
    • 直接为实例对象添加一个属性

      //初始化状态,也是定义两个key,分别为isHot与wind
      state = {isHot:false,wind:'微风'}
      
  • 更新:setState触发更新,先获取再更新

    // 获取原来的状态
    const isHot = this.state.isHot
    //严重注意:状态必须通过setState进行更新,且更新是一种合并,不是替换
    this.setState({isHot:!isHot})
    

example:(一句话,点击里面的天气修改天气)

  • 编写了一个 Weather 类组件
  • 类组件有一个构造方法,在整个类渲染的时候,只会调用一次
  • 类组件有一个render() 方法来转换虚拟DOM为真实DOM,渲染页面,每当组件的状态变化,都会被调用,调用次数为n次
  • 类组件的 h1 中定义了一个onClick 点击事件,触发changeWeather() 方法,原型方法,用于修改数据
<script type="text/babel">
        //1.创建类组件
        class Weather extends React.Component{
            //构造器调用几次? ———— 1次
            constructor(props){
                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
                // 页面根据state中的数据判断显示,通过点击修改状态从而修改显示
                return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'}{wind}</h1>
            }
            //changeWeather调用几次? ———— 是上述的点击事件,即点几次调几次
            // changeWeather 是 Weather的原型对象上,供实例使用
            changeWeather(){

                //由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用,类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined(注意点)
                //获取原来的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>

注意点:

  • 组件中render方法中的 this组件实例对象

  • 组件自定义的方法(原型方法),因为并不是手动new出的实例来调用,而是onclick直接调用,所有 this 为undefined,解决办法有两个

    • 强制绑定this。通过函数对象的bind(),上述解决选择了这种,在构造函数中写了

      this.changeWeather = this.changeWeather.bind(this) 
      
    • 箭头函数,如果不在构造函数中强制绑定的方式,可以将函数定义为箭头函数,即可解决

      changeWeather = ()=>{
      	const isHot = this.state.isHot
      	this.setState({isHot:!isHot})
      }
      
  • 不能直接修改或更新状态数据

  • setState() 方法的 更新是一种合并,不是替换

上述example简写版本:

<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>
		}
		//自定义方法————要用赋值语句的形式+箭头函数,不用绑定的方式
		changeWeather = () => {
			const isHot = this.state.isHot
			this.setState({isHot:!isHot})
		}
	}
	//2.渲染组件到页面
	ReactDOM.render(<Weather/>,document.getElementById('test'))	
</script>
props

props 是通过标签属性从组件外向组件内传递变化的数据,简单来说就是在使用组件的时候,传递参数的保持位置,例如:

像上述直接使用组件时候,都没带标签属性:

ReactDOM.render(<Person/>,document.getElementById('test1')) // props就为{}

若使用组件时候,带标签属性时,实例对象中 的props就相应的有对应值:

ReactDOM.render(<Person name="tom" age={18} sex="女"/>,document.getElementById('test2')) // props就为{name: "jerry", age: 19, sex: "男"}

请添加图片描述

example:

<!-- 准备好“容器” -->
<div id="test1"></div>
<div id="test2"></div>
<div id="test3"></div>
<script type="text/babel">
	//创建组件
	class Person extends React.Component{
		render(){
            // this是实例对象,this.props就拿到了传入的值
			const {name,age,sex} = this.props
			return (
				<ul>
					<li>姓名:{name}</li>
					<li>性别:{sex}</li>
					<li>年龄:{age+1}</li>
				</ul>
			)
		}
	}
	//渲染组件到页面
	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:'女'}
    // 可以通过key=value的形式传入,也可直接通过 展开运算符 进行全部属性的传入
	// ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>,document.getElementById('test3'))
	ReactDOM.render(<Person {...p}/>,document.getElementById('test3'))
</script>

注意点:

  • 组件内部不要修改props数据

  • 组件在使用时,每个组件对象都会有props属性,根据传入的不同,props会相应有所不同

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

标签属性的值, 可以对其类型进行限制,比较设置字段的类型与默认值,需要借助prop-types.js 包

例如:

<!-- 引入prop-types,用于对组件标签属性进行限制 -->
<script type="text/javascript" src="../js/prop-types.js"></script>

//对标签属性进行类型、必要性的限制
Person.propTypes = {
     name:PropTypes.string.isRequired, //限制name必传,且为字符串
     sex:PropTypes.string,//限制sex为字符串
     age:PropTypes.number,//限制age为数值
     speak:PropTypes.func,//限制speak为函数
   }
//指定默认标签属性值
Person.defaultProps = {
     sex:'男',//sex默认值为男
     age:18 //age默认值为18
}

如果在传参的时候,没有按照规定的要求进行传参,会报相应的 Warning

请添加图片描述

refs

该属性主要用于组件内的标签通过定义ref属性来标识自己

例如:

  • render() 中返回了一个 输入框 一个按钮,点击按钮弹出输入框里面的输入数据
  • 普通的标签,可以根据 getxxById() / (${}) 来获取到标签
  • 但是标签在组件中,靠着这种方式难免就不行了
  • 就需要借助 ref ,例如:
<script type="text/babel">
	//创建组件
	class Demo extends React.Component{
		//展示左侧输入框的数据
		showData = ()=>{
            // 获取到输入框 ,根据其value获取到输入框的值
			const {input1} = this.refs
			alert(input1.value)
		}
        // 渲染页面
		render(){
			return(
				<div>
                	// 使用ref input1来标识自己
					<input ref="input1" type="text" placeholder="点击按钮提示数据"/>&nbsp;
					<button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
				</div>
			)
		}
	}
	//渲染组件到页面
	ReactDOM.render(<Demo a="1" b="2"/>,document.getElementById('test'))
</script>

上述是一种字符串创建ref的方式,创建ref有三种方式:

  • 字符串创建

    // 标识
    <input ref="input1" type="text" placeholder="点击按钮提示数据"/>&nbsp;
    // 获取
    const {input1} = this.refs
    
  • 回调形式的ref(回调次数不止一次,两次)

    // 标识
    第一种方式:<input ref={c => this.input1 = c } type="text" placeholder="点击按钮提示数据"/>&nbsp;
    第二种方式:<input ref={(c)=>{this.input1 = c;}} type="text"/><br/><br/>
        
    <input ref={c => this.input2 = c } type="text" placeholder="失去焦点提示数据"/>&nbsp;
    
    // 获取
    const {input1} = this
    const {input2} = this
    
  • createRef创建ref容器

    // 先创建,直接在类组件中创建实例对象myRef
    myRef = React.createRef()
    // 标识
    <input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>&nbsp;
    // 使用,这里就不是直接 this.myRef.value,这里的this.myRef.current才是上面的input标签
    alert(this.myRef.current.value);
    

虽然Ref能够来获取到组件中的标签,不要过度使用Ref,关于上述的实现,还有另外一种方式

当标签有 onXXX 事件(通过onXxx属性指定事件处理函数),React对其处理使用的是自定义(合成)事件, 而不是使用的原生DOM事件,通过事件委托方式处理的(委托给组件最外层的元素),即 事件处理,通过event.target得到发生事件的DOM元素对象

举个例子:(输入框失去焦点的时候,弹出输入框的值)

1)借助 ref 的实现:(通过ref来获取事件的DOM元素)

// 创建
myRef = React.createRef()
//展示输入框的数据
showData = ()=>{
    //this.myRef.current 获取 DOM元素
	alert(this.myRef.current.value);
}
render(){
	return(
		<div>
			<input onBlur={this.showData}  ref={this.myRef} type="text" placeholder="失去焦点提示数据"/>&nbsp;
		</div>
	)
}

2)借助 事件处理 实现:(通过 event.target 来获取到事件的DOM元素)

//展示输入框的数据
showData = (event)=>{
    // event.target 就获取到 DOM元素
		alert(event.target.value);
}
render(){
	return(
		<div>
			<input onBlur={this.showData} type="text" placeholder="失去焦点提示数据"/>&nbsp;
		</div>
	)
}

总结:关于一些 onXXXX 事件,完全可以通过 event.target来实现 DOM元素的获取,不需要借助Ref ,很好的避免了使用Ref(不要过度使用Ref)

组件生命周期介绍

生命周期的理解:

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

在react 16.x 版本及之前的组件生命周期

请添加图片描述

说明:(主要有以下三个阶段)

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

    • constructor():构造器
    • componentWillMount():组件将要挂载的钩子
    • render():将虚拟DOM转换为真实DOM,准备渲染页面
    • componentDidMount() :组件挂载完毕的钩子,常用一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
  • 更新阶段:(由组件内部 this.setSate() 或 父组件 render 触发)

  • componentWillReceiveProps:父组件 render 触发时候会调用,父组件传给子组件的prop,组件将要接收新的props的钩子

  • shouldComponentUpdate():控制组件更新的“阀门”,唯一需要给予返回值的钩子,true才执行下面的

  • componentWillUpdate():组件将要更新的钩子

  • render(): =====> 必须使用的一个

  • componentDidUpdate():组件更新完毕的钩子

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

  • componentWillUnmount():组件将要卸载的钩子,一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息

其中很重要也是用的最多的:

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

example:(可以看控制台的输出来更好的看一看到具体的执行的流程~)

<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">
		//创建组件
		class Count extends React.Component{

			//构造器
			constructor(props){
				console.log('Count---constructor');
				super(props)
				//初始化状态
				this.state = {count:0}
			}
			
			//组件将要挂载的钩子
			componentWillMount(){
				console.log('Count---componentWillMount');
			}

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

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

			//控制组件更新的“阀门”
			shouldComponentUpdate(){
				console.log('Count---shouldComponentUpdate');
				return true
			}

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

			//组件更新完毕的钩子
			componentDidUpdate(){
				console.log('Count---componentDidUpdate');
			}
			
			//加1按钮的回调
			add = ()=>{
				//获取原状态
				const {count} = this.state
				//更新状态
				this.setState({count:count+1})
			}

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

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

			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>
</body>
</html>
生命周期介绍:(新)

react 17.x开始,生命周期变为如下,与旧的生命周期,有以下变化:

  • 抛弃三个钩子(带Will的): componentWillMount、componentWillReceiveProps、componentWillUpdate
  • 新增一个钩子:getSnapshotBeforeUpdate

请添加图片描述

说明:(也主要有以下三个阶段)

  • 初始化阶段:(由ReactDOM.render()触发—初次渲染)(其实就是将 getDerivedStateFromProps 替换 componentWillMount)

    • constructor():构造器
    • getDerivedStateFromProps ():若state的值在任何时候都取决于props,可以使用该钩子,需要给予返回值
    • render():将虚拟DOM转换为真实DOM,准备渲染页面
    • componentDidMount() :组件挂载完毕的钩子,常用一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
  • 更新阶段:(由组件内部 this.setSate() 或 父组件 render 触发)(getDerivedStateFromProps代替了componentWillReceiveProps,getSnapshotBeforeUpdate代替了componentWillUpdate)

    • getDerivedStateFromProps:若state的值在任何时候都取决于props,可以使用该钩子,需要给予返回值
    • shouldComponentUpdate():控制组件更新的“阀门”,需要给予返回值的钩子,true才执行下面的
    • render(): 必须使用的一个
    • getSnapshotBeforeUpdate:在更新之前获取快照的钩子,具备返回值
    • componentDidUpdate():组件更新完毕的钩子
  • 卸载组件:(由ReactDOM.unmountComponentAtNode()触发)

    • componentWillUnmount():组件将要卸载的钩子,一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息

虽然生命周期变了,但是很重要也是用的最多的其实并没有变~

  • render:初始化渲染或更新渲染调用
  • componentDidMount:开启监听, 发送ajax请求
  • componentWillUnmount:做一些收尾工作

example:(可以看控制台的输出来更好的看一看到具体的执行的流程~)

<body>
	<!-- 准备好一个“容器” -->
	<div id="test"></div>
	
	<!-- 引入react核心库 -->
	<script type="text/javascript" src="../js/17.0.1/react.development.js"></script>
	<!-- 引入react-dom,用于支持react操作DOM -->
	<script type="text/javascript" src="../js/17.0.1/react-dom.development.js"></script>
	<!-- 引入babel,用于将jsx转为js -->
	<script type="text/javascript" src="../js/17.0.1/babel.min.js"></script>

	<script type="text/babel">
		//创建组件
		class Count extends React.Component{
			//构造器
			constructor(props){
				console.log('Count---constructor');
				super(props)
				//初始化状态
				this.state = {count:0}
			}
			//若state的值在任何时候都取决于props,那么可以使用getDerivedStateFromProps
			static getDerivedStateFromProps(props,state){
				console.log('getDerivedStateFromProps',props,state);
				return null
			}

			//在更新之前获取快照
			getSnapshotBeforeUpdate(){
				console.log('getSnapshotBeforeUpdate');
				return 'example'
			}

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

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

			//控制组件更新的“阀门”
			shouldComponentUpdate(){
				console.log('Count---shouldComponentUpdate');
				return true
			}

			//组件更新完毕的钩子
			componentDidUpdate(preProps,preState,snapshotValue){
				console.log('Count---componentDidUpdate',preProps,preState,snapshotValue);
			}
			
			//加1按钮的回调
			add = ()=>{
				//获取原状态
				const {count} = this.state
				//更新状态
				this.setState({count:count+1})
			}

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

			//强制更新按钮的回调
			force = ()=>{
				this.forceUpdate()
			}
			
			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 count={199}/>,document.getElementById('test'))
	</script>
</body>

总结

在React开发项目时,组件是非常重要的元素,组件中的三大元素与生命周期也是面试经常问的东西,需要很好的掌握

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值