React

第1章:React入门

新版本推荐使用函数组件

React简介

官网

  1. 英文官网:React

  1. 中文官网: React 官方中文文档

介绍描述

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

  1. 由Facebook开源

React的特点

  1. 声明式编码

  1. 组件化编码

  1. React Native 编写原生应用

  1. 高效(优秀的Diffing算法)

React高效的原因

  1. 使用虚拟(virtual)DOM,不总是直接操作页面真实DOM。

  1. DOM Diffing算法, 最小化页面重绘。

React的基本使用

相关js库

  1. react.js:React核心库。

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

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

创建虚拟DOM的两种方式

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

代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="test"></div>
//核心库最先引用
    <script type="text/javascript" src="../js/react.development.js"></script>
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <script type="text/javascript" src="../js/babel.min.js"></script>

    <script type="text/babel">
        // 创建虚拟dom
        const VDOM =React.createElement('h1',{id:"title"},React.createElement('span',{},'hello react'))
        // 渲染到网页
        ReactDOM.render(VDOM,document.getElementById('test'));

    </script>
</body> 
</html>

虚拟DOM与真实DOM

  1. React提供了一些API来创建一种 “特别” 的一般js对象

  • const VDOM = React.createElement('xx',{id:'xx'},'xx')

  • 上面创建的就是一个简单的虚拟DOM对象

  1. 虚拟DOM对象最终都会被React转换为真实的DOM

  1. 我们编码时基本只需要操作react的虚拟DOM相关数据, react会转换为真实DOM变化而更新界。

关于虚拟DOM

  1. 本质是Object类型的对象(一般对象)

  2. 虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在用,无需真实DOM上那么多的属性。

  3. 虚拟DOM最终会被React转化为真实DOM,呈现在页面上。

JSX语法规则

                        1.定义虚拟DOM时,不要写引号。

                        2.标签中混入JS表达式时要用{}。

                        3.样式的类名指定不要用class,要用className。

                        4.内联样式,要用style={{key:value}}的形式去写。

                        5.只有一个根标签

                        6.标签必须闭合

                        7.标签首字母

                                (1).若小写字母开头,则将该标签转为html中同名元素,若html中无该标签对应的同名元素,则报错。

                                (2).若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。

代码:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>jsx语法规则</title>
	<style>
		.title{
			background-color: orange;
			width: 200px;
		}
	</style>
</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" >
		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>
</body>
</html>

【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章:React面向组件编程

React定义组件:

 函数式组件(新版本可以使用state、ref)


            执行了ReactDOM.render(<MyComponent/>.......之后
                    1.React解析组件标签,找到了MyComponent组件。
                    2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。
 

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

类式组件

   
            执行了ReactDOM.render(<MyComponent/>.......之后
                    1.React解析组件标签,找到了MyComponent组件。
                    2.发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。
                    3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。

<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>

组件实例的三大属性

1.state

        state作为数据不能直接进行修改需要通过setState方法

复杂写法:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>state</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 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>
			}

			//changeWeather调用几次? ———— 点几次调几次
			changeWeather(){
				//changeWeather放在哪里? ———— Weather的原型对象上,供实例使用
				//由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
				//类中的方法默认开启了局部的严格模式,所以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>
</body>
</html>

简单写法:(使用箭头函数作为方法使用,无需使用构造器)

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>state简写方式</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 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>
</body>
</html>

2.props

props基本使用:

          渲染组件时

        ...数组/对象: ReactDOM.render(<Person {...p}/>,document.getElementById('test3')),会解析出数据

        将数据(key value方式)写在标签上,React会传递props作为参数,组件可以通过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>

	<script type="text/babel">
		//创建组件
		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>
				)
			}
		}
		//渲染组件到页面
		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>
</body>
</html>
props的限制
        方式一:通过实例的方式进行限制(相对麻烦)

        类名.propsStyle={ }方式是指类型限制

        类名.defaultProps={ } 方式设置默认数据(没传值即为默认数据)

<!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{
			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>
				)
			}
		}
		//对标签属性进行类型、必要性的限制
		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
		}
		//渲染组件到页面
		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>
</body>
</html>
          方式二:通过static静态方法(推荐)
<!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>

通过父组件进行兄弟组件之间的传值

首先在父组件操作state,创建一个函数,将此函数作为数据传递给子组件,传值的子组件创建一个方法,里面调用父亲传递过来的方法的方法,将需要传递的数据作为父组件的函数参数,交给父组件函数进行处理,再将处理好的数据传递给需要接受数据的子组件

3.Ref

        方式一:字符串形式的Ref:(老方法,最简单)

           设置:标签设置ref属性及属性值     

           使用: 通过refs来获取ref属性值(代码太多会造成refs太累)

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>1_字符串形式的ref</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">
		//创建组件
		class Demo extends React.Component{
			//展示左侧输入框的数据
			showData = ()=>{
				const {input1} = this.refs
				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>
</body>
</html>
        方式二:回调函数式的Ref:
               回调函数写在标签上的ref上:在回调中的参数赋值给当前的组件实例
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>1_字符串形式的ref</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">
		//创建组件
		class Demo extends React.Component{
			//展示左侧输入框的数据
			showData = ()=>{
				const {input1} = this
				alert(input1.value)
			}
			//展示右侧输入框的数据
			showData2 = ()=>{
				const {input2} = this
				alert(input2.value)
			}
			render(){
				return(
					<div>
						<input ref={currentNode => this.input1 = currentNode } 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>
</body>
</html>
                写一个函数作为回调:

                同上述,将回调内容写在函数中,ref属性设为这个函数回调   

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>3_回调ref中回调执行次数的问题</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">
		//创建组件
		class Demo extends React.Component{

			state = {isHot:false}

			showInfo = ()=>{
				const {input1} = this
				alert(input1.value)
			}

			changeWeather = ()=>{
				//获取原来的状态
				const {isHot} = this.state
				//更新状态
				this.setState({isHot:!isHot})
			}

			saveInput = (c)=>{
				this.input1 = c;
				console.log('@',c);
			}

			render(){
				const {isHot} = this.state
				return(
					<div>
						<h2>今天天气很{isHot ? '炎热':'凉爽'}</h2>
						{/*<input ref={(c)=>{this.input1 = c;console.log('@',c);}} type="text"/><br/><br/>*/}
						<input ref={this.saveInput} type="text"/><br/><br/>
						<button onClick={this.showInfo}>点我提示输入的数据</button>
						<button onClick={this.changeWeather}>点我切换天气</button>
					</div>
				)
			}
		}
		//渲染组件到页面
		ReactDOM.render(<Demo/>,document.getElementById('test'))
	</script>
</body>
</html>
       方式三:createRef方式:(新属性)

              class组件中添加数据    myRef = React.createRef()

              标签当前通过调用myRef方法作为属性值

                使用:this.myRef.current.value 即可获取节点内容

              注意:一个createRef 只能存储一个ref节点       

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>4_createRef</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">
		//创建组件
		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>
</body>
</html>

       

React的事件处理

        不要过度使用ref,如果是当前节点获取当前数据可以用event.target替换获得节点

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>事件处理</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">
		//创建组件
		class Demo extends React.Component{
			/* 
				(1).通过onXxx属性指定事件处理函数(注意大小写)
						a.React使用的是自定义(合成)事件, 而不是使用的原生DOM事件 —————— 为了更好的兼容性
						b.React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) ————————为了的高效
				(2).通过event.target得到发生事件的DOM元素对象 ——————————不要过度使用ref
			 */
			//创建ref容器
			myRef = React.createRef()
			myRef2 = React.createRef()

			//展示左侧输入框的数据
			showData = (event)=>{
				console.log(event.target);
				alert(this.myRef.current.value);
			}

			//展示右侧输入框的数据
			showData2 = (event)=>{
				alert(event.target.value);
			}

			render(){
				return(
					<div>
						<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>&nbsp;
						<button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
						<input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>&nbsp;
					</div>
				)
			}
		}
		//渲染组件到页面
		ReactDOM.render(<Demo a="1" b="2"/>,document.getElementById('test'))
	</script>
</body>
</html>

表单的组件分类

1、受控组件

2、非受控组件

组件柯里化    

1.高阶函数

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

                  1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。

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

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

2.函数的柯里化:

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

    function sum(a){

                            return(b)=>{

                                return (c)=>{

                                    return a+b+c

                                }

                            }

                        }

组件的生命周期

简单理解:出生到死亡的过程

生命周期就是在组件创建过程中在特定的时间被调用,做特定的工作

生命周期流程图(旧)

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

  1. constructor() 构造器函数         (开发基本不用)
  2. componentWillMount()        组件将要挂载       
  3. render() 
  4. componentDidMount()       (常用)组件挂载完成          

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

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

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

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

  1. componentWillUnmount()   (常用)组件即将销毁的钩子        一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息                   

        

生命周期流程图(新)

相对于旧版本,废除了三个钩子componentWillMount,componentWillUpdate,增加了两个钩子getDerivedStateFromProps,getSnapshotBeforeUpdate

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

                                1.  constructor()

                                2.  getDerivedStateFromProps 会接受一个props作为参数,会返回null/state对象,返回的数据不能在进行修改

                                3.  render()

                                4.  componentDidMount() =====> 常用  组件挂载完成的钩子

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

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

                                1.  getDerivedStateFromProps

                                2.  shouldComponentUpdate()  是否允许修改

                                3.  render()

                                4.  getSnapshotBeforeUpdate  可以通过这个钩子获取数据修改前,比如判断当前滚动条的高度

                                5.  componentDidUpdate()  组件修改后的钩子

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

                                1.  componentWillUnmount()  =====> 常用  组件即将销毁的钩子

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

DOM的Diffing算法

验证代码:

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>验证diff算法</title>
</head>
<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 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>
</body>
</html>

原理图

Key的使用原因

1). react/vue中的key有什么作用?key的内部原理是什么?为什么遍历列表时,key最好不要用index?

 虚拟DOM中key的作用:

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

                    2). 详细的说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,

                   随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:

                                    a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:

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

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

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

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

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

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

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

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

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

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

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

开发中如何选择key?:

                                1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。

                                2.如果确定只是简单的展示数据,用index也是可以的。 

代码展示:

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>key的作用</title>
</head>
<body>
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel -->
<script type="text/javascript" src="../js/babel.min.js"></script>

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

3章:React应用(基于React脚手架)

3.1.1. react脚手架

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

3.1.2. 创建项目并启动

第一步,全局安装:npm i -g create-react-app

第二步,切换到想创项目的目录,使用命令:create-react-app hello-react

第三步,进入项目文件夹:cd hello-react

第四步,启动项目:npm start

3.1.3. react脚手架项目结构

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 ------- 入口文件

logo.svg ------- logo图

reportWebVitals.js

--- 页面性能分析文件(需要web-vitals库的支持)

setupTests.js

---- 组件单元测试的文件(需要jest-dom库的支持)

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

1. 拆分组件: 拆分界面,抽取组件

2. 实现静态组件: 使用组件实现静态页面效果

3. 实现动态组件

3.1 动态显示初始化数据

3.1.1 数据类型

3.1.2 数据名称

3.1.2 保存在哪个组件?

3.2 交互(从绑定事件监听开始)

4章:React ajax

4.1.1. 前置说明

  1. React本身只关注于界面, 并不包含发送ajax请求的代码
  2. 前端应用需要通过ajax请求与后台进行交互(json数据)
  3. react应用中需要集成第三方ajax(或自己封装)

4.1.2. 常用的ajax请求库

  1. jQuery: 比较重, 如果需要另外引入不建议使用
  2. axios: 轻量级, 建议使用
      1. 封装XmlHttpRequest对象的ajax
      2.  promise风格
      3. 可以用在浏览器端和node服务器端

4.2. axios

下载    npm i axios

引入    import axios from 'axios'

4.2.1. 文档

GitHub - axios/axios: Promise based HTTP client for the browser and node.js

4.2.2.简单API

GET请求
axios.get('/user?ID=12345')
  .then(function (response) {
    console.log(response.data);
  })
  .catch(function (error) {
    console.log(error);
  });

axios.get('/user', {
    params: {
      ID: 12345
    }
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });

POST请求
axios.post('/user', {
  firstName: 'Fred',
  lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});

todoList案例相关知识点

        1.拆分组件、实现静态组件,注意:className、style的写法

        2.动态初始化列表,如何确定将数据放在哪个组件的state中?

                    ——某个组件使用:放在其自身的state中

                    ——某些组件使用:放在他们共同的父组件state中(官方称此操作为:状态提升)

        3.关于父子之间通信:

                1.【父组件】给【子组件】传递数据:通过props传递

                2.【子组件】给【父组件】传递数据:通过props传递,要求父提前给子传递一个函数

        4.注意defaultChecked 和 checked的区别,类似的还有:defaultValue 和 value

            defaultChecked 只执行一次

            checked 会写死当前状态 需要用onChange改变状态

        5.状态在哪里,操作状态的方法就在哪里

github搜索案例相关知识点

        1.设计状态时要考虑全面,例如带有网络请求的组件,要考虑请求失败怎么办。

        2.ES6小知识点:解构赋值+重命名

                    let obj = {a:{b:1}}

                    const {a} = obj; //传统解构赋值

                    const {a:{b}} = obj; //连续解构赋值

                    const {a:{b:value}} = obj; //连续解构赋值+重命名       

        4.fetch发送请求(关注分离的设计思想)

                    try {

                        const response= await fetch(`/api1/search/users2?q=${keyWord}`)

                        const data = await response.json()

                        console.log(data);

                    } catch (error) {

                        console.log('请求出错',error);

                    }

消息订阅与发布机制(解决各组件之间的传递数据)

                       下载pubsub

                        先订阅,再发布(理解:有一种隔空对话的感觉)

                        发布:PubSub.publish(传递的数据)

                        订阅:PubSub.subscribe('订阅名称',(_,接收数据)=>{
                   
                                })

                    使用数据的组件去订阅,传递数据的进行发布

                    要在组件的componentWillUnmount中取消订阅

    订阅组件:

import React, { Component } from 'react'
import PubSub from 'pubsub-js'
import './index.css'

export default class List extends Component {

	state = { //初始化状态
		users:[], //users初始值为数组
		isFirst:true, //是否为第一次打开页面
		isLoading:false,//标识是否处于加载中
		err:'',//存储请求相关的错误信息
	} 

	componentDidMount(){
		this.token = PubSub.subscribe('atguigu',(_,stateObj)=>{
			this.setState(stateObj)
		})
	}

	componentWillUnmount(){
		PubSub.unsubscribe(this.token)
	}

	render() {
		const {users,isFirst,isLoading,err} = this.state
		return (
			<div className="row">
				{
					isFirst ? <h2>欢迎使用,输入关键字,随后点击搜索</h2> :
					isLoading ? <h2>Loading......</h2> :
					err ? <h2 style={{color:'red'}}>{err}</h2> :
					users.map((userObj)=>{
						return (
							<div key={userObj.id} className="card">
								<a rel="noreferrer" href={userObj.html_url} target="_blank">
									<img alt="head_portrait" src={userObj.avatar_url} style={{width:'100px'}}/>
								</a>
								<p className="card-text">{userObj.login}</p>
							</div>
						)
					})
				}
			</div>
		)
	}
}

 发布组件:

import React, { Component } from 'react'
import PubSub from 'pubsub-js'
import axios from 'axios'

export default class Search extends Component {

	search = ()=>{
		//获取用户的输入(连续解构赋值+重命名)
		const {keyWordElement:{value:keyWord}} = this
		//发送请求前通知List更新状态
		PubSub.publish('atguigu',{isFirst:false,isLoading:true})
		//发送网络请求
		axios.get(`/api1/search/users?q=${keyWord}`).then(
			response => {
				//请求成功后通知List更新状态
				PubSub.publish('atguigu',{isLoading:false,users:response.data.items})
			},
			error => {
				//请求失败后通知App更新状态
				PubSub.publish('atguigu',{isLoading:false,err:error.message})
			}
		)
	}

	render() {
		return (
			<section className="jumbotron">
				<h3 className="jumbotron-heading">搜索github用户</h3>
				<div>
					<input ref={c => this.keyWordElement = c} type="text" placeholder="输入关键词点击搜索"/>&nbsp;
					<button onClick={this.search}>搜索</button>
				</div>
			</section>
		)
	}
}

第五章: 路由的使用(旧版本)

路由的基本使用

            1.明确好界面中的导航区、展示区

            2. 引入路由 import {Link,Route} from 'react-router-dom'

            3.导航区的a标签改为Link标签(路由跳转)

                        <Link to="/xxxxx">Demo</Link>

            4.展示区写Route标签进行路径的匹配(展示路由)

                        <Route path='/xxxx' component={Demo}/>

            5.<App>的最外侧包裹了一个<BrowserRouter>或<HashRouter>

                推荐使用:BrowserRouter,一个单页面只有一个路由包裹

路由组件与一般组件

            1.写法不同:

                        一般组件:<Demo/>

                        路由组件:<Route path="/demo" component={Demo}/>

            2.存放位置不同:

                        一般组件:components

                        路由组件:pages

            3.接收到的props不同:

                        一般组件:写组件标签时传递了什么,就能收到什么

                        路由组件:接收到三个固定的属性

                                            history:

                                                        go: ƒ go(n)

                                                        goBack: ƒ goBack()

                                                        goForward: ƒ goForward()

                                                        push: ƒ push(path, state)

                                                        replace: ƒ replace(path, state)

                                            location:

                                                        pathname: "/about"

                                                        search: ""

                                                        state: undefined

                                            match:

                                                        params: {}

                                                        path: "/about"

                                                        url: "/about"

NavLink与封装NavLink

                <NavLink to="/xxxxx">Demo</NavLink>

                NavLink可以实现路由链接的高亮,通过activeClassName指定样式名

                可以对NavLink进行二次封装,封装接收数据可以使用...对象方式

      MyNavLink.jsx  

import React, { Component } from 'react'
import {NavLink} from 'react-router-dom'

export default class MyNavLink extends Component {
	render() {
		// console.log(this.props);
		return (
			<NavLink activeClassName="atguigu" className="list-group-item" {...this.props}/>
		)
	}
}

                NavLink会自动接受当前标签中的数据给到props,存放在props中的children属性中

                通过this.props.children的使用

Switch的使用

                Switch解决一个路径匹配多个路由组件,设置后只会显示先匹配的路由组件

    <Switch>
		<Route path="/about" component={About}/>
		<Route path="/home" component={Home}/>
		<Route path="/home" component={Test}/>							
	</Switch>							

                1.通常情况下,path和component是一一对应的关系。

                2.Switch可以提高路由匹配效率(单一匹配)。

解决多级路径刷新页面样式丢失的问题

                多级路由举例:将/lihe路由路径转换为 like/lihe 再去刷新界面可能会导致界面样式失效

                1.public/index.html 中 引入样式时不写 ./ 写 / (常用,当前根路径路径public)

                2.public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用,绝对路径public,只适用于react脚手架)

                3.使用HashRouter (不推荐,路由导航中会带#号)

 路由的严格匹配与模糊匹配及replace不留痕迹模式

                1.默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)

                2.开启严格匹配:

                        <Route exact={true} path="/about" component={About}/>

                        简写:<Route exact path="/about" component={About}/>

                3.严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由

路由的push模式和replace模式

    路由默认为push模式 网页可以回退

    replace模式(网页访问不留痕迹)

                <Route replace={true} path="/about" component={About}/>

                简写:<Route replace  path="/about" component={About}/>

Redirect的使用    

                1.一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由

                    (可以理解为默认路由)

                2.具体编码:

                        <Switch>

                            <Route path="/about" component={About}/>

                            <Route path="/home" component={Home}/>

                            <Redirect to="/about"/>

                        </Switch>

嵌套路由

                1.注册子路由时要写上父路由的path值

                2.路由的匹配是按照注册路由的顺序进行的

                例如:跳转home下的message路由

                跳转路径必须携带home路径  

                        <Route path="/home/message" component={Message}/>

                           home:父亲路由

                           message:子路由

向路由组件传递参数

                1.params参数

                            路由链接(携带参数):<Link to='/demo/test/tom/18'}>详情</Link>

                            注册路由(声明接收):<Route path="/demo/test/:name/:age" component={Test}/>

                            接收参数:this.props.match.params

                2.search参数

                            路由链接(携带参数):<Link to='/demo/test?name=tom&age=18'}>详情</Link>

                            注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>

                            接收参数:this.props.location.search

                            备注:获取到的search是urlencoded编码字符串,需要借助querystring解析

                                    引入 import qs from 'querystring'

                                    接收参数会带? 可以通过slice方式截掉

                                    qs.parse() 将编码字符串转为对象

                                    qs.stringfy() 将对象转化为字符编码

                3.state参数

                            路由链接(携带参数):<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>

                            注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>

                            接收参数:this.props.location.state

                            备注:刷新也可以保留住参数(原理就是state传参方式是操作浏览器的history的方法,删除缓存数据数据就会消失了)

               

   

编程式路由导航

                    借助this.prosp.history对象上的API对操作路由跳转、前进、后退

                            -this.props.history.push() 跳转,不覆盖页面记录

                            -this.props.history.replace() 跳转,覆盖上个页面记录

                            -this.props.history.goBack() 后退

                            -this.props.history.goForward() 前进

                            -this.props.history.go(n)  指定正负数前进或后退

                       

一般组件使用路由组件上的方法

        通过引入withRouter

                        import {withRouter} from 'react-router-dom'

        将一般组件暴漏 export default withRouter(组件名)

import React, { Component } from 'react'
import {withRouter} from 'react-router-dom'

class Header extends Component {

	back = ()=>{
		this.props.history.goBack()
	}

	forward = ()=>{
		this.props.history.goForward()
	}

	go = ()=>{
		this.props.history.go(-2)
	}

	render() {
		console.log('Header组件收到的props是',this.props);
		return (
			<div className="page-header">
				<h2>React Router Demo</h2>
				<button onClick={this.back}>回退</button>&nbsp;
				<button onClick={this.forward}>前进</button>&nbsp;
				<button onClick={this.go}>go</button>
			</div>
		)
	}
}

export default withRouter(Header)

//withRouter可以加工一般组件,让一般组件具备路由组件所特有的API
//withRouter的返回值是一个新组件

        作用:将一般组件加入路由组件的方法


 

BrowserRouter与HashRouter的区别

            1.底层原理不一样:

                        BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。

                        HashRouter使用的是URL的哈希值。

            2.path表现形式不一样

                        BrowserRouter的路径中没有#,例如:localhost:3000/demo/test

                        HashRouter的路径包含#,例如:localhost:3000/#/demo/test

            3.刷新后对路由state参数的影响

                        (1).BrowserRouter没有任何影响,因为state保存在history对象中。

                        (2).HashRouter刷新后会导致路由state参数的丢失!!!

            4.备注:HashRouter可以用于解决一些路径错误相关的问题。

antd的按需引入+自定主题(尚硅谷)

            1.安装依赖:yarn add react-app-rewired customize-cra babel-plugin-import less less-loader

            2.修改package.json

                    ....

                        "scripts": {

                            "start": "react-app-rewired start",

                            "build": "react-app-rewired build",

                            "test": "react-app-rewired test",

                            "eject": "react-scripts eject"

                        },

                    ....

            3.根目录下创建config-overrides.js

                    //配置具体的修改规则

                    const { override, fixBabelImports,addLessLoader} = require('customize-cra');

                    module.exports = override(

                        fixBabelImports('import', {

                            libraryName: 'antd',

                            libraryDirectory: 'es',

                            style: true,

                        }),

                        addLessLoader({

                            lessOptions:{

                                javascriptEnabled: true,

                                modifyVars: { '@primary-color': 'green' },

                            }

                        }),

                    );

                4.备注:不用在组件里亲自引入样式了,即:import 'antd/dist/antd.css'应该删掉

antd的按需引入+自定主题(李可 5版本antd)

             antd基本使用

                网址:https://ant-design.antgroup.com/docs/react/use-with-create-react-app-cn

                引入antd: npm install antd --save

                                    yarn add antd

                修改 src/App.js,引入 antd 的按钮组件。(无需单独引入样式)

                    import React from 'react';

                    import { Button } from 'antd';

                    const App: React.FC = () => (

                    <div className="App">

                        <Button type="primary">Button</Button>

                    </div>

                    );

                    export default App;

                自定义主题:

                    引入import { ConfigProvider } from 'antd';

                    将<App>组件外包裹<ConfigProvider>标签

                

 // 引入核心库

                        import React from "react";



                        // 引入dom

                        import ReactDOM from "react-dom/client";



                        // 引入组件

                        import App from './App.jsx'

                        // 引入路由

                        import { BrowserRouter } from "react-router-dom";

                        // 自定义主题标签

                        import { ConfigProvider } from 'antd';



                        // 渲染

                        const root = ReactDOM.createRoot(document.getElementById('root'));

                        root.render(

                        <ConfigProvider theme={{ token: { colorPrimary: '#00b96b' } }}>

                        <BrowserRouter>

                            <App/>

                        </BrowserRouter>

                        </ConfigProvider>

                       

                        );

配置主题

在 5.0 版本中我们把影响主题的最小元素称为 Design Token。通过修改 Design Token,我们可以呈现出各种各样的主题或者组件。通过在 ConfigProvider 中传入 theme 属性,可以配置主题。在升级 v5 后,将默认使用 v5 的主题.


其他UI库及显示核心配置

        React ui库 AntDesign(pc端)

         Vue ui库 Element-ui(pc端)    vant(移动端)  

         npm eject 可以暴漏react核心配置文件(无法回退)

路由的使用(新版本)

React Router 6 快速上手

1.概述

  1. React Router 以三个不同的包发布到 npm 上,它们分别为:

    1. react-router: 路由的核心库,提供了很多的:组件、钩子。

    2. react-router-dom: 包含react-router所有内容,并添加一些专门用于 DOM 的组件,例如 <BrowserRouter>等 

    3. react-router-native: 包括react-router所有内容,并添加一些专门用于ReactNative的API,例如:<NativeRouter>等。

  2. 与React Router 5.x 版本相比,改变了什么?

    1. 内置组件的变化:移除<Switch/> ,新增 <Routes/>等。

    2. 语法的变化:component={About} 变为 element={<About/>}等。

    3. 新增多个hook:useParamsuseNavigateuseMatch等。

    4. 官方明确推荐函数式组件了!!!

      ......

2.Component

1. <BrowserRouter>

  1. 说明:<BrowserRouter>用于包裹整个应用。

  2. 示例代码:

    import React from "react";
    import ReactDOM from "react-dom";
    import { BrowserRouter } from "react-router-dom";
    ​
    ReactDOM.render(
      <BrowserRouter>
        {/* 整体结构(通常为App组件) */}
      </BrowserRouter>,root
    );

2. <HashRouter>

  1. 说明:作用与<BrowserRouter>一样,但<HashRouter>修改的是地址栏的hash值。

  2. 备注:6.x版本中<HashRouter><BrowserRouter> 的用法与 5.x 相同。

3. <Routes/> 与 <Route/>

  1. v6版本中移出了先前的<Switch>,引入了新的替代者:<Routes>

  2. <Routes><Route>要配合使用,且必须要用<Routes>包裹<Route>

  3. <Route> 相当于一个 if 语句,如果其路径与当前 URL 匹配,则呈现其对应的组件。

  4. <Route caseSensitive> 属性用于指定:匹配时是否区分大小写(默认为 false)。

  5. 当URL发生变化时,<Routes>都会查看其所有子<Route> 元素以找到最佳匹配并呈现组件 。

  6. <Route> 也可以嵌套使用,且可配合useRoutes()配置 “路由表” ,但需要通过 <Outlet> 组件来渲染其子路由。

  7. 示例代码:

    <Routes>
        /*path属性用于定义路径,element属性用于定义当前路径所对应的组件*/
        <Route path="/login" element={<Login />}></Route>
    ​
            /*用于定义嵌套路由,home是一级路由,对应的路径/home*/
        <Route path="home" element={<Home />}>
           /*test1 和 test2 是二级路由,对应的路径是/home/test1 或 /home/test2*/
          <Route path="test1" element={<Test/>}></Route>
          <Route path="test2" element={<Test2/>}></Route>
            </Route>
        
            //Route也可以不写element属性, 这时就是用于展示嵌套的路由 .所对应的路径是/users/xxx
        <Route path="users">
           <Route path="xxx" element={<Demo />} />
        </Route>
    </Routes>

4. <Link>

  1. 作用: 修改URL,且不发送网络请求(路由链接)。

  2. 注意: 外侧需要用<BrowserRouter><HashRouter>包裹。

  3. 示例代码:

    import { Link } from "react-router-dom";
    ​
    function Test() {
      return (
        <div>
            <Link to="/路径">按钮</Link>
        </div>
      );
    }

5. <NavLink>

  1. 作用: 与<Link>组件类似,且可实现导航的“高亮”效果。

  2. 示例代码:

    // 注意: NavLink默认类名是active,下面是指定自定义的class
    ​
    //自定义样式
    <NavLink
        to="login"
        className={({ isActive }) => {
            console.log('home', isActive)
            return isActive ? 'base one' : 'base'
        }}
    >login</NavLink>
    ​
    /*
        默认情况下,当Home的子组件匹配成功,Home的导航也会高亮,
        当NavLink上添加了end属性后,若Home的子组件匹配成功,则Home的导航没有高亮效果。
    */
    <NavLink to="home" end >home</NavLink>

6. <Navigate> 重定向(说白了就是默认路由可以进行判断)

  1. 作用:只要<Navigate>组件被渲染,就会修改路径,切换视图。

  2. replace属性用于控制跳转模式(push 或 replace,默认是push)。

  3. 示例代码:

    import React,{useState} from 'react'
    import {Navigate} from 'react-router-dom'
    ​
    export default function Home() {
        const [sum,setSum] = useState(1)
        return (
            <div>
                <h3>我是Home的内容</h3>
                {/* 根据sum的值决定是否切换视图 */}
                {sum === 1 ? <h4>sum的值为{sum}</h4> : <Navigate to="/about" replace={true}/>}
                <button onClick={()=>setSum(2)}>点我将sum变为2</button>
            </div>
        )
    }

7. <Outlet>

  1. <Route>产生嵌套时,渲染其对应的后续子路由。

  2. 示例代码:

    //根据路由表生成对应的路由规则
    const element = useRoutes([
      {
        path:'/about',
        element:<About/>
      },
      {
        path:'/home',
        element:<Home/>,
        children:[
          {
            path:'news',
            element:<News/>
          },
          {
            path:'message',
            element:<Message/>,
          }
        ]
      }
    ])
    ​
    //Home.js
    
    import React from 'react'
    import {NavLink,Outlet} from 'react-router-dom'
    ​
    export default function Home() {
        return (
            <div>
                <h2>Home组件内容</h2>
                <div>
                    <ul className="nav nav-tabs">
                        <li>
                            <NavLink className="list-group-item" to="news">News</NavLink>
                        </li>
                        <li>
                            <NavLink className="list-group-item" to="message">Message</NavLink>
                        </li>
                    </ul>
                    {/* 指定路由组件呈现的位置 */}
                    <Outlet />
                </div>
            </div>
        )
    }

3.Hooks

1. useRoutes()

  1. 作用:根据路由表,动态创建<Routes><Route>

  2. 示例代码:路由表配置:

src/routes/index.js


import About from '../pages/About'
import Home from '../pages/Home'
import {Navigate} from 'react-router-dom'
​
export default [
    {
        path:'/about',
        element:<About/>
    },
    {
        path:'/home',
        element:<Home/>
    },
    {
        path:'/',
        element:<Navigate to="/about"/>
    }
]
​
//App.jsx

import React from 'react'
import {NavLink,useRoutes} from 'react-router-dom'
import routes from './routes'
​
export default function App() {
    //根据路由表生成对应的路由规则
    const element = useRoutes(routes)
    return (
        <div>
            ......
      {/* 注册路由 element代替一堆routes包裹route */}
      {element}
          ......
        </div>
    )
}

2. useNavigate()

  1. 作用:返回一个函数用来实现编程式导航。

  2. 示例代码:

    import React from 'react'
    import {useNavigate} from 'react-router-dom'
    ​
    export default function Demo() {
      const navigate = useNavigate()
      const handle = () => {
        //第一种使用方式:指定具体的路径
        navigate('/login', {
          replace: false,
          state: {a:1, b:2}
        }) 
        //第二种使用方式:传入数值进行前进或后退,类似于5.x中的 history.go()方法
        navigate(-1)
      }
      
      return (
        <div>
          <button onClick={handle}>按钮</button>
        </div>
      )
    }

3. useParams()

  1. 作用:回当前匹配路由的params参数,类似于5.x中的match.params

  2. 示例代码:

    import React from 'react';
    import { Routes, Route, useParams } from 'react-router-dom';
    import User from './pages/User.jsx'
    
    function ProfilePage() {
      // 获取URL中携带过来的params参数
      let { id } = useParams();
    }
    
    function App() {
      return (
        <Routes>
          <Route path="users/:id" element={<User />}/>
        </Routes>
      );
    }

4. useSearchParams()

  1. 作用:用于读取和修改当前位置的 URL 中的查询字符串。

  2. 返回一个包含两个值的数组,内容分别为:当前的seaech参数、更新search的函数。

  3. 示例代码:

    import React from 'react'
    import {useSearchParams} from 'react-router-dom'
    
    export default function Detail() {
    	const [search,setSearch] = useSearchParams()
    	const id = search.get('id')
    	const title = search.get('title')
    	const content = search.get('content')
    	return (
    		<ul>
    			<li>
    				<button onClick={()=>setSearch('id=008&title=哈哈&content=嘻嘻')}>点我更新一下收到的search参数</button>
    			</li>
    			<li>消息编号:{id}</li>
    			<li>消息标题:{title}</li>
    			<li>消息内容:{content}</li>
    		</ul>
    	)
    }

5. useLocation()

  1. 作用:获取当前 location 信息,对标5.x中的路由组件的location属性。

  2. 示例代码:

    import React from 'react'
    import {useLocation} from 'react-router-dom'
    
    export default function Detail() {
    	const x = useLocation()
    	console.log('@',x)
      // x就是location对象: 
    	/*
    		{
          hash: "",
          key: "ah9nv6sz",
          pathname: "/login",
          search: "?name=zs&age=18",
          state: {a: 1, b: 2}
        }
    	*/
    	return (
    		<ul>
    			<li>消息编号:{id}</li>
    			<li>消息标题:{title}</li>
    			<li>消息内容:{content}</li>
    		</ul>
    	)
    }
    

6. useMatch()

  1. 作用:返回当前匹配信息,对标5.x中的路由组件的match属性。

  2. 示例代码:

    <Route path="/login/:page/:pageSize" element={<Login />}/>
    <NavLink to="/login/1/10">登录</NavLink>
    
    export default function Login() {
      const match = useMatch('/login/:x/:y')
      console.log(match) //输出match对象
      //match对象内容如下:
      /*
      	{
          params: {x: '1', y: '10'}
          pathname: "/LoGin/1/10"  
          pathnameBase: "/LoGin/1/10"
          pattern: {
          	path: '/login/:x/:y', 
          	caseSensitive: false, 
          	end: false
          }
        }
      */
      return (
      	<div>
          <h1>Login</h1>
        </div>
      )
    }

7. useInRouterContext()

作用:如果组件在 <Router> 的上下文中呈现,则 useInRouterContext 钩子返回 true,否则返回 false。

8. useNavigationType()

  1. 作用:返回当前的导航类型(用户是如何来到当前页面的)。

  2. 返回值:POPPUSHREPLACE

  3. 备注:POP是指在浏览器中直接打开了这个路由组件(刷新页面)。

9. useOutlet()

  1. 作用:用来呈现当前组件中渲染的嵌套路由。

  2. 示例代码:

    const result = useOutlet()
    console.log(result)
    // 如果嵌套路由没有挂载,则result为null
    // 如果嵌套路由已经挂载,则展示嵌套的路由对象

10.useResolvedPath()

  1. 作用:给定一个 URL值,解析其中的:path、search、hash值。

        

redux的使用(熟悉流程图可直接查看最终版)

redux简单理解:

       redux文件包括:

                        action文件 (action对象,包括类型和数据)

                        store文件        (核心文件,在其中创建store,供给容器使用)

                        reducers文件        (初始化store数据,对数据进行加工,就是根基类型对数据操作的地方)

                        constant文件  (自定义action中的类型,方便读写)

 react- redux   简单理解

                根本:connect方法会创建一个容器组件

                        该容器组件用来与redux进行数据交互

                        容器组件会包括一个组件,组件可以通过props来获取数据

                

原理图                

1.求和案例_redux精简版

        (1).去除Count组件自身的状态

        (2).src下建立:

                        -redux

                            -store.js

                            -count_reducer.js

        (3).store.js:

                    1).引入redux中的createStore函数,创建一个store

                    2).createStore调用时要传入一个为其服务的reducer

                    3).记得暴露store对象

        (4).count_reducer.js:

                    1).reducer的本质是一个函数,接收:preState,action,返回加工后的状态

                    2).reducer有两个作用:初始化状态,加工状态

                    3).reducer被第一次调用时,是store自动触发的,

                                    传递的preState是undefined,

                                    传递的action是:{type:'@@REDUX/INIT_a.2.b.4}

        (5).在index.js中监测store中状态的改变,一旦发生改变重新渲染<App/>

                备注:redux只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写。

                获取仓库数据:store.getState()


 

2.求和案例_redux完整版

        新增文件:

            1.count_action.js 专门用于创建action对象

            2.constant.js 放置容易写错的type值



 

 3.求和案例_redux异步action版

         (1).明确:延迟的动作不想交给组件自身,想交给action

         (2).何时需要异步action:想要对状态进行操作,但是具体的数据靠异步任务返回。

         (3).具体编码:

                    1).yarn add redux-thunk,并配置在store中

                    2).创建action的函数不再返回一般对象,而是一个函数,该函数中写异步任务。

                    3).异步任务有结果后,分发一个同步的action去真正操作数据。

         (4).备注:异步action不是必须要写的,完全可以自己等待异步任务的结果了再去分发同步action。





 

 4.求和案例_react-redux基本使用

            (1).明确两个概念:

                        1).UI组件:不能使用任何redux的api,只负责页面的呈现、交互等。

                        2).容器组件:负责和redux通信,将结果交给UI组件。

            (2).如何创建一个容器组件————靠react-redux 的 connect函数

                            connect(mapStateToProps,mapDispatchToProps)(UI组件)

                                -mapStateToProps:映射状态,返回值是一个对象

                                -mapDispatchToProps:映射操作状态的方法,返回值是一个对象

            (3).备注1:容器组件中的store是靠props传进去的,而不是在容器组件中直接引入

            (4).备注2:mapDispatchToProps,也可以是一个对象


 5.求和案例_react-redux优化

            (1).容器组件和UI组件整合一个文件

            (2).无需自己给容器组件传递store

                    引入store仓库

                    引入Provider

                            import {Provider} from 'react-redux'

                    给<App/>包裹一个<Provider store={store}>即可。

            (3).使用了react-redux后也不用再自己检测redux中状态的改变了,容器组件可以自动完成这个工作。

            (4).mapDispatchToProps也可以简单的写成一个对象

                    例:

                        // 简写方法

                        export default connect(

                            state=>({count:state}),

                            // 对象方式写法(会自动派发action,即自动调用dispatch方法)

                            {

                                jia:createIncrementAction,

                                jian:createDecrementAction,

                                jiaAsync:createIncrementAsyncAction

                            }

                           

                            )(CountUI)

            (5).一个组件要和redux“打交道”要经过哪几步?

                            (1).定义好UI组件---不暴露

                            (2).引入connect生成一个容器组件,并暴露,写法如下:

                                    connect(

                                        state => ({key:value}), //映射状态

                                        {key:xxxxxAction} //映射操作状态的方法

                                    )(UI组件)

                            (4).在UI组件中通过this.props.xxxxxxx读取和操作状态



 

 6.求和案例_react-redux数据共享版

            (1).定义一个Pserson组件,和Count组件通过redux共享数据。

            (2).为Person组件编写:reducer、action,配置constant常量。

            (3).重点:Person的reducer和Count的Reducer要使用combineReducers进行合并,

                    合并后的总状态是一个对象!!!

            (4).交给store的是总reducer,最后注意在组件中取出状态的时候,记得“取到位”。

7.求和案例_react-redux开发者工具的使用

            (1).yarn add redux-devtools-extension

            (2).store中进行配置

                    import {composeWithDevTools} from 'redux-devtools-extension'

                    const store = createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))

8.求和案例_react-redux最终版

            (1).所有变量名字要规范,尽量触发对象的简写形式。

            (2).reducers文件夹中,编写index.js专门用于汇总并暴露所有的reducer

redux及react-redux个人总结:

安装插件

                npm install redux

                npm i react-redux

redux-devtools插件

                npm i  redux-devtools-extension

redux-thunk插件(异步action的使用)

        npm i redux-thunk

文件框架

containers文件:容器组件

        1.count文件:(count.js)
import React, { Component } from 'react'
//引入action
import {
	increment,
	decrement,
	incrementAsync
} from '../../redux/actions/count'
//引入connect用于连接UI组件与redux
import {connect} from 'react-redux'

//定义UI组件
class Count extends Component {

	state = {carName:'奔驰c63'}

	//加法
	increment = ()=>{
		const {value} = this.selectNumber
		this.props.increment(value*1)
	}
	//减法
	decrement = ()=>{
		const {value} = this.selectNumber
		this.props.decrement(value*1)
	}
	//奇数再加
	incrementIfOdd = ()=>{
		const {value} = this.selectNumber
		if(this.props.count % 2 !== 0){
			this.props.increment(value*1)
		}
	}
	//异步加
	incrementAsync = ()=>{
		const {value} = this.selectNumber
		this.props.incrementAsync(value*1,500)
	}

	render() {
		//console.log('UI组件接收到的props是',this.props);
		return (
			<div>
				<h2>我是Count组件,下方组件总人数为:{this.props.renshu}</h2>
				<h4>当前求和为:{this.props.count}</h4>
				<select ref={c => this.selectNumber = c}>
					<option value="1">1</option>
					<option value="2">2</option>
					<option value="3">3</option>
				</select>&nbsp;
				<button onClick={this.increment}>+</button>&nbsp;
				<button onClick={this.decrement}>-</button>&nbsp;
				<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>&nbsp;
				<button onClick={this.incrementAsync}>异步加</button>&nbsp;
			</div>
		)
	}
}

//使用connect()()创建并暴露一个Count的容器组件
export default connect(
	state => ({
		count:state.count,
		personCount:state.persons.length
	}),
	{increment,decrement,incrementAsync}
)(Count)

        2.person文件(person.js)
import React, { Component } from 'react'
//随机id
import {nanoid} from 'nanoid'
import {connect} from 'react-redux'
import {addPerson} from '../../redux/actions/person'

class Person extends Component {

	addPerson = ()=>{
		const name = this.nameNode.value
		const age = this.ageNode.value*1

		const personObj = {id:nanoid(),name,age}
        //调用容器组件的方法
		this.props.addPerson(personObj)
		this.nameNode.value = ''
		this.ageNode.value = ''
	}

	render() {
		return (
			<div>
				<h2>我是Person组件,上方组件求和为{this.props.count}</h2>
				<input ref={c=>this.nameNode = c} type="text" placeholder="输入名字"/>
				<input ref={c=>this.ageNode = c} type="text" placeholder="输入年龄"/>
				<button onClick={this.addPerson}>添加</button>
				<ul>
					{
						this.props.persons.map((p)=>{
							return <li key={p.id}>{p.name}--{p.age}</li>
						})
					}
				</ul>
			</div>
		)
	}
}
//connect会自动派发dispatch,state为store的数据
export default connect(
	state => ({
		persons:state.persons,
		count:state.count
	}),//映射状态
//key:value方式(简写)
	{addPerson}//映射操作状态的方法
//Person为组件
)(Person)

      redux文件

                1.actions文件:          
                        count.js
/* 
	该文件专门为Count组件生成action对象
*/
import {INCREMENT,DECREMENT} from '../constant'

//同步action,就是指action的值为Object类型的一般对象
export const increment = data => ({type:INCREMENT,data})
export const decrement = data => ({type:DECREMENT,data})

//异步action,就是指action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的。
export const incrementAsync = (data,time) => {
	return (dispatch)=>{
		setTimeout(()=>{
			dispatch(increment(data))
		},time)
	}
}
                        person.js
import {ADD_PERSON} from '../constant'

//创建增加一个人的action动作对象
export const addPerson = personObj => ({type:ADD_PERSON,data:personObj})
                2.reducers文件
                        index.js
/* 
	该文件用于汇总所有的reducer为一个总的reducer
*/
//引入combineReducers,用于汇总多个reducer
import {combineReducers} from 'redux'
//引入为Count组件服务的reducer
import count from './count'
//引入为Person组件服务的reducer
import persons from './person'

//汇总所有的reducer变为一个总的reducer
export default  combineReducers({
	count,
	persons
})
                        count.js
/* 
	1.该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数
	2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
*/
import {INCREMENT,DECREMENT} from '../constant'

const initState = 0 //初始化状态
export default function countReducer(preState=initState,action){
	// console.log('countReducer@#@#@#');
	//从action对象中获取:type、data
	const {type,data} = action
	//根据type决定如何加工数据
	switch (type) {
		case INCREMENT: //如果是加
			return preState + data
		case DECREMENT: //若果是减
			return preState - data
		default:
			return preState
	}
}
                        person.js
import {ADD_PERSON} from '../constant'

//初始化人的列表
const initState = [{id:'001',name:'tom',age:18}]

export default function personReducer(preState=initState,action){
	// console.log('personReducer@#@#@#');
	const {type,data} = action
	switch (type) {
		case ADD_PERSON: //若是添加一个人
			//preState.unshift(data) //此处不可以这样写,这样会导致preState被改写了,personReducer就不是纯函数了。
			return [data,...preState]
		default:
			return preState
	}
}
                3.constant.js
/* 
	该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错
*/
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
export const ADD_PERSON = 'add_person'
                4.store.js
/* 
	该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/

//引入createStore,专门用于创建redux中最为核心的store对象
//applyMiddleware中间件配合redux-thunk使用
import {createStore,applyMiddleware} from 'redux'
//引入汇总之后的reducer
import reducer from './reducers'
//引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
//引入redux-devtools-extension
import {composeWithDevTools} from 'redux-devtools-extension'

//携带异步方法的创建store
//暴露创建store  composeWithDevTools用于redux-devtools的使用
//export default createStore(reducer,composeWithDevTools(applyMiddleware(thunk)))

//不携带异步方法的创建store
export default createStore(reducer,composeWithDevTools())
                App.jsx
import React, { Component } from 'react'
import Count from './containers/Count' //引入的Count的容器组件
import Person from './containers/Person' //引入的Person的容器组件

export default class App extends Component {
	render() {
		return (
			<div>
				<Count/>
				<hr/>
				<Person/>
			</div>
		)
	}
}
        index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import store from './redux/store'
import {Provider} from 'react-redux'

ReactDOM.render(
	/* 此处需要用Provider包裹App,目的是让App所有的后代容器组件都能接收到store */
	<Provider store={store}>
		<App/>
	</Provider>,
	document.getElementById('root')
)

文件打包

        npm run build

插件 serve可以配置一台服务器

   npm i serve -g

运行打包文件

serve build(文件名)

React扩展

1. setState

setState更新状态的2种写法
    (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函数中读取
                  

import React, { Component } from 'react'

export default class Demo extends Component {

	state = {count:0}

	add = ()=>{
		//对象式的setState
		/* //1.获取原来的count值
		const {count} = this.state
		//2.更新状态
		this.setState({count:count+1},()=>{
			console.log(this.state.count);
		})
		//console.log('12行的输出',this.state.count); //0 */

		//函数式的setState
		this.setState( state => ({count:state.count+1}))
	}

	render() {
		return (
			<div>
				<h1>当前求和为:{this.state.count}</h1>
				<button onClick={this.add}>点我+1</button>
			</div>
		)
	}
}

2. lazyLoad

路由组件的lazyLoad

    //1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
    const Login = lazy(()=>import('@/pages/Login'))
    
    //2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面
    <Suspense fallback={<h1>loading.....</h1>}>
        <Switch>
            <Route path="/xxx" component={Xxxx}/>
            <Redirect to="/login"/>
        </Switch>
    </Suspense>


3. Hooks

1. React Hook/Hooks是什么?
(1). Hook是React 16.8.0版本增加的新特性/新语法
(2). 可以让你在函数组件中使用 state 以及其他的 React 特性
2. 三个常用的Hook
(1). State Hook: React.useState()
(2). Effect Hook: React.useEffect()
(3). Ref Hook: React.useRef()
3. State Hook
(1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
(2). 语法: const [xxx, setXxx] = React.useState(initValue)  
(3). useState()说明:
        参数: 第一次初始化指定的值在内部作缓存
        返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
(4). setXxx()2种写法:
        setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
        setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
4. Effect Hook
(1). Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
(2). React中的副作用操作:
        发ajax请求数据获取
        设置订阅 / 启动定时器
        手动更改真实DOM
(3). 语法和说明: 
        useEffect(() => { 
          // 在此可以执行任何带副作用操作
          return () => { // 在组件卸载前执行
            // 在此做一些收尾工作, 比如清除定时器/取消订阅等
          }
        }, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
    
(4). 可以把 useEffect Hook 看做如下三个函数的组合
        componentDidMount()
        componentDidUpdate()
        componentWillUnmount() 
5. Ref Hook
(1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
(2). 语法: const refContainer = useRef()
(3). 作用:保存标签对象,功能与React.createRef()一样


4. Fragment

使用

<Fragment><Fragment>
<></>

作用

可以不用必须有一个真实的DOM根标签了


5. Context

理解

一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信

使用

1) 创建Context容器对象:
    const XxxContext = React.createContext()  
    
2) 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
    <xxxContext.Provider value={数据}>
        子组件
    </xxxContext.Provider>
    
3) 后代组件读取数据:
​
    //第一种方式:仅适用于类组件 
      static contextType = xxxContext  // 声明接收context
      this.context // 读取context中的value数据
      
    //第二种方式: 函数组件与类组件都可以
      <xxxContext.Consumer>
        {
          value => ( // value就是context中的value数据
            要显示的内容
          )
        }
      </xxxContext.Consumer>
​
//新版本,通过useContext
   const {数据}=React.useContext(XxxContext)  
   直接使用即可
   
​

注意

在应用开发中一般不用context, 一般都用它的封装react插件


6. 组件优化

Component的2个问题

  1. 只要执行setState(),即使不改变状态数据, 组件也会重新render() ==> 效率低

  2. 只当前组件重新render(), 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低

效率高的做法

只有当组件的state或props数据发生改变时才重新render()

原因

Component中的shouldComponentUpdate()总是返回true

解决

办法1: 
    重写shouldComponentUpdate()方法
    比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false
办法2:  
    使用PureComponent
    PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true
    注意: 
        只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false  
        不要直接修改state数据, 而是要产生新数据
项目中一般使用PureComponent来优化

实际就是PureComponent替换掉Component

import React, { PureComponent } from 'react'
import './index.css'

export default class Parent extends PureComponent {

	state = {carName:"奔驰c36",stus:['小张','小李','小王']}

	addStu = ()=>{
		/* const {stus} = this.state
		stus.unshift('小刘')
		this.setState({stus}) */

		const {stus} = this.state
		this.setState({stus:['小刘',...stus]})
	}

	changeCar = ()=>{
		//this.setState({carName:'迈巴赫'})

		const obj = this.state
		obj.carName = '迈巴赫'
		console.log(obj === this.state);
		this.setState(obj)
	}

	/* shouldComponentUpdate(nextProps,nextState){
		// console.log(this.props,this.state); //目前的props和state
		// console.log(nextProps,nextState); //接下要变化的目标props,目标state
		return !this.state.carName === nextState.carName
	} */

	render() {
		console.log('Parent---render');
		const {carName} = this.state
		return (
			<div className="parent">
				<h3>我是Parent组件</h3>
				{this.state.stus}&nbsp;
				<span>我的车名字是:{carName}</span><br/>
				<button onClick={this.changeCar}>点我换车</button>
				<button onClick={this.addStu}>添加一个小刘</button>
				<Child carName="奥拓"/>
			</div>
		)
	}
}

class Child extends PureComponent {

	/* shouldComponentUpdate(nextProps,nextState){
		console.log(this.props,this.state); //目前的props和state
		console.log(nextProps,nextState); //接下要变化的目标props,目标state
		return !this.props.carName === nextProps.carName
	} */

	render() {
		console.log('Child---render');
		return (
			<div className="child">
				<h3>我是Child组件</h3>
				<span>我接到的车是:{this.props.carName}</span>
			</div>
		)
	}
}


7. render props

如何向组件内部动态传入带内容的结构(标签)?

Vue中: 
    使用slot技术, 也就是通过组件标签体传入结构  <A><B/></A>
React中:
    使用children props: 通过组件标签体传入结构
    使用render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性

children props

<A>
  <B>xxxx</B>
</A>
{this.props.children}
问题: 如果B组件需要A组件内的数据, ==> 做不到 

render props

<A render={(data) => <C data={data}></C>}></A>
A组件: {this.props.render(内部state数据)}
C组件: 读取A组件传入的数据显示 {this.props.data} 
import React, { Component } from 'react'
import './index.css'
import C from '../1_setState'

export default class Parent extends Component {
	render() {
		return (
			<div className="parent">
				<h3>我是Parent组件</h3>
				<A render={(name)=><C name={name}/>}/>
			</div>
		)
	}
}

class A extends Component {
	state = {name:'tom'}
	render() {
		console.log(this.props);
		const {name} = this.state
		return (
			<div className="a">
				<h3>我是A组件</h3>
				{this.props.render(name)}
			</div>
		)
	}
}

class B extends Component {
	render() {
		console.log('B--render');
		return (
			<div className="b">
				<h3>我是B组件,{this.props.name}</h3>
			</div>
		)
	}
}


8. 错误边界

理解:

错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面

特点:

只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误

使用方式:

getDerivedStateFromError配合componentDidCatch

// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
    console.log(error);
    // 在render之前触发
    // 返回新的state
    return {
        hasError: true,
    };
}
​
//子组件出错时调用
componentDidCatch(error, info) {
    // 统计页面的错误。发送请求发送到后台去
    console.log(error, info);
}
import React, { Component } from 'react'
import Child from './Child'

export default class Parent extends Component {

	state = {
		hasError:'' //用于标识子组件是否产生错误
	}

	//当Parent的子组件出现报错时候,会触发getDerivedStateFromError调用,并携带错误信息
	static getDerivedStateFromError(error){
		console.log('@@@',error);
		return {hasError:error}
	}

	componentDidCatch(){
		console.log('此处统计错误,反馈给服务器,用于通知编码人员进行bug的解决');
	}

	render() {
		return (
			<div>
				<h2>我是Parent组件</h2>
				{this.state.hasError ? <h2>当前网络不稳定,稍后再试</h2> : <Child/>}
			</div>
		)
	}
}

9. 组件通信方式总结

组件间的关系:
  • 父子组件

  • 兄弟组件(非嵌套组件)

  • 祖孙组件(跨级组件)

几种通信方式:
    1.props:
        (1).children props
        (2).render props
    2.消息订阅-发布:
        pubs-sub、event等等
    3.集中式管理:
        redux、dva等等
    4.conText:
        生产者-消费者模式
比较好的搭配方式:
    父子组件:props
    兄弟组件:消息订阅-发布、集中式管理
    祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值