1. react介绍

react是用来构建用户界面的javascript UI库,它只关注视图层。

2. react特点

  • 声明式编码
  • 组件化编码
  • 高效渲染
  • 可以使用reactnative编写移动端开发应用

这里需要注意声明式编码与原来命令式编码有啥区别:

通俗来讲命令式编码做任何一件事都需要亲力亲为才能实现,反而声明式编码只需要告诉我需要什么,至于怎么做不需要自己亲自实现,都是交给框架来做。从这可以看出声明式编码让开发者更加高效,代码更加简洁。

react高效原因?

采用虚拟DOM,不是直接去操作dom,使用dom diffing算法,计算出需要更新dom, 从而减少页面绘制

3. react开发相关核心库

  • react.js (react核心库)
  • react-dom (提供react操作dom扩展库)
  • babel.js (将jsx语法转换成标准的javascript代码)

4. 创建虚拟dom两种方式

    4.1 采用jsx语法来创建

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>jsx创建虚拟DOM</title>
</head>
<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 id="title">
				<span>Hello,React</span>
			</h1>
		)
		//2.渲染虚拟DOM到页面
		ReactDOM.render(VDOM,document.getElementById('test'))
	</script>
</body>
</html>

  4.2 采用js创建(这种不推荐)

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>使用js创建虚拟DOM</title>
</head>
<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>

	<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>
</body>
</html>

虚拟DOM与真实DOM区别:

  • 虚拟dom本质就是一个普通一般对象
  • 虚拟dom是由react来维护的,身上的属性相比真实dom要少很多,因此虚拟dom比真实dom更加`轻`
  • 虚拟dom最终都会由react转换成真实dom,最终呈现在页面上

5. jsx语法规则

jsx全称 Javascript xml,  react定义它类似为xml一种js扩展,本质就是React.createElement()语法糖.

jsx语法规则:

  • 定义虚拟dom时不能用引号
  • 在使用类class时,要用className
  • 在使用内联style时,要用{{key:value}}
  • 在虚拟dom中使用js表达式时,要使用{}
  • 只能有一个根标签
  • 标签必须要闭合
  • 标签首字母:1. 如果标签首字母是小写,那么react会将该标签转换成html标签去寻找,如果未找到就会报错。2. 如果标签首字母是大写,那么会去渲染该组件,如果未找到就会报错。

6. 模块与组件,模块化与组件化理解

    6.1 模块

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

        为啥需要拆分模块:随着业务逻辑复杂,代码量越来越多

        作用:复用js代码,简化js编码,提高js运行效率

    6.2 组件

       组件:实现某个局部功能的代码和资源集合(html/css/js/png)

       为啥需要组件: 由于页面功能复杂性,需要拆分成多个组件

       作用:提高代码复用率,高效开发

   6.3 模块化

      通俗来讲就是将一个应用拆分成多个模块来编写就称为模块化

   6.4 组件化

      通俗来讲就是将某个功能拆分成多个组件来编写就称为组件化

不管是组件化还是模块化都是为了提高代码复用率、降低耦合、简化代码编写、提高开发效率

7. 创建组件两种方式

  •    函数式组件         
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>1_函数式组件</title>
</head>
<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">
		//1.创建函数式组件
		function MyComponent(){
			console.log(this); //此处的this是undefined,因为babel编译后开启了严格模式
			return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
		}
		//2.渲染组件到页面
		ReactDOM.render(<MyComponent/>,document.getElementById('test'))
	</script>
</body>
</html>

ReactDOM.render()这个过程做了些啥? React首先会去解析这个组件,发现这个组件是函数定义的,那么会调用这个函数,将返回虚拟DOM转换成真实dom呈现在页面上.

  • 类式组件
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>2_类式组件</title>
</head>
<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">
		//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'))
	</script>
</body>
</html>

ReactDOM.render()这个过程做了啥? react解析组件标签,发现组件是类来定义的,那么会通过new方式来创建组件实例,然后在根据实例调用原型上render()方法,将返回虚拟dom转换成真实dom呈现在页面上。

这里需要注意的是:组件名需要首字母大写,组件只有一个根标签,标签需要闭合。

8.组件三大核心属性

   8.1 state

       理解: state是组件最重要属性,它是一个对象(可以包含多个key,value 键值对),组件被称为`状态机`,通过更新组件的state来对应界面更新(render()方法被调用,重新渲染组件)。

      作用:更新组件的UI

这里对state使用需要注意的地方:

  • state不能直接更新后修改,只能调用setState()方法来更新state,setState()方法更新state并不是完全替换之前state的值,而是合并。
  • 函数组件没有state,应此无法在函数组件使用state这个属性,当然你要想在函数使用state可以采用react提供hook来完(useState()),这在后面会详细说。
  • setState()方法可以接收2种参数,一种是对象,另外一种是一个回调函数。传递回调函数就是为了异步更新。应为我们state和props的值都有可能是异步

更新的值。

(1). setState(stateChange, [callback])------对象式的setState
            1.stateChange为状态改变对象(该对象可以体现出状态的更改)
            2.callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
					
	(2). setState(updater, [callback])------函数式的setState
            1.updater为返回stateChange对象的函数。
            2.updater可以接收到state和props。
            4.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
总结:
		1.对象式的setState是函数式的setState的简写方式(语法糖)
		2.使用原则:
				(1).如果新状态不依赖于原状态 ===> 使用对象方式
				(2).如果新状态依赖于原状态 ===> 使用函数方式
				(3).如果需要在setState()执行后获取最新的状态数据, 
					要在第二个callback函数中读取

  8.2 props

        理解:每个组件对象上都有一个props属性(全称properties),组件对象上所有属性都会保存到props上

        作用:用来组件之间传递数据,组件之间传递方向是由外到内(父亲->孩子),并且数据流是单向的,因此我们不应该在内部去修改props,这样会影响外部数据,导致数据发生混乱,不容易理解。

       谈到props这里需要提一下相关一个库props-type,这个库是用来限制props传递数据类型。

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>对props进行限制</title>
</head>
<body>
	<!-- 准备好一个“容器” -->
	<div id="test1"></div>
	<div id="test2"></div>
	<div id="test3"></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>
	<!-- 引入prop-types,用于对组件标签属性进行限制 -->
	<script type="text/javascript" src="../js/prop-types.js"></script>

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

			constructor(props){
				//构造器是否接收props,是否传递给super,取决于:是否希望在构造器中通过this访问props
				// console.log(props);
				super(props)
				console.log('constructor',this.props);
			}

			//对标签属性进行类型、必要性的限制
			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>
				)
			}
		}

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

函数组件是如何使用props:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>对props进行限制</title>
</head>
<body>
	<!-- 准备好一个“容器” -->
	<div id="test1"></div>
	<div id="test2"></div>
	<div id="test3"></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>
	<!-- 引入prop-types,用于对组件标签属性进行限制 -->
	<script type="text/javascript" src="../js/prop-types.js"></script>

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

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

    8.3 ref

          理解:组件内标签可以通过ref来标示自己

          作用:在某些情况下,需要在典型数据流之外强制修改子组件,子组件可能是组件实例,也可能是一个dom元素

         我们应该避免使用refs任何可以用声明式完成的功能,常见使用场景(管理焦点、使用第三方的dom、触发强制动画)

        使用方式有三种:

        8.3.1 通过React.createRef()来创建,并且将值附属到组件实例上。这样就可以随意使用了

myRef = React.createRef();

<input ref={this.myRef}/>

使用方式:
this.myRef.current来获取当前的值,current属性的值可能是一个dom元素,也可能是一个组件实例。这得看ref作用于元素类型(dom元素、组件实例)

      8.3.2 通过回调refs方式来创建

       不同于createRef()传递refs属性,它会传递一个函数。这个函数接收React组件实例或者一个DOM元素,以便他们在其他地方存取与访问。

//1.采用内联箭头函数
<button onClick={c=>this.inputRef=c}>测试</button>
//2.第二种方式采用class field方式
inputRef = (c) => {
   this.inputRefs=c
}
<button onClick={this.inputRef}>测试</button>
两者区别在于:第一种方式每次渲染都会创建一个新的函数实例,会被调用2次。第一次去把当前值设置为null,然后第二次设置新的值。第二种方式我们使用实例属性,只会创建一次,可以直接更新当前的值。

这里做个总结:当组件挂载,refs的值会被传入DOM元素,当组件卸载,refs的值被设置为null,在componentDidMount或componentDidUpdate生命周期钩子触发之前更新。函数组件没有refs,应此无法在函数组件使用refs,当然要想在函数组件使用refs,react也提供hook解决这个问题(useRef()方法),后面详细说 。