本文介绍: 尚硅谷天禹老师react全家桶课程

文章目录

第1章 React简介

React可以克服原生JS的以下缺点:

  1. 原生JS操作DOM繁琐且效率低,因为用DOM-API操作UI;
  2. JS直接操作DOM会使浏览器进行大量的重绘重排;
  3. 原生JS没有组件编码方案代码复用率低

react开发者工具:Chrome插件 React Developer Tool(注意安装来源为facebook的)

1.1 React的特点

  1. 采用组件模式声明编码提高开发效率及组件复用率;
  2. 在React Native可以使用React语法进行移动开发
  3. 使用虚拟DOM+优秀的Diffing算法,尽量减少与真实DOM的交互
    在这里插入图片描述
    对比两次生成虚拟DOM,如果重复,直接用页面之前有的DOM,而不是全部重绘真实DOM

1.2 引入文件

1.3 JSX

全称: JavaScript XML,是react定义的一种类似于XML的JS扩展语法本质React.createElement(component, props, ...children)方法的语法糖。JXS最终产生的虚拟DOM就是一个JS对象

1.3.1 为什么要用JSX

1.3.2 JSX语法规则

const VDOM=(
            <div&gt;
                <h1&gt;前端js框架列表</h1&gt;
                <ul>
                    {
                        data.map((item,index)=>{
                            return <li key={index}>{item}</li>
                        })
                    }
                </ul>               
            </div>           
        )

遇到以 { 开头的代码,以JS语法解析,且标签中的js表达式必须用{ }包含比如<ul>中的{}中的{index}{item}

注:key={index}:在进行组件遍历时候必须要加一个key来区分每个组件

辨析【js表达式】和【js语句 (代码)】

1.4 虚拟DOM

  1. 本质是Object,即一般对象(不是数组对象和函数对象)
  2. 虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是react内部在用,无需真实DOM中那么多属性
  3. 虚拟DOM最终会被react转换为真实DOM呈现页面

1.5 模块组件

1.5.1 模块

1.5.2 组件

第2章 React面向组件编程

2.1 创建组件

2.1.1 函数式组件

用于简单组件(无state

<script type="text/babel">
		// 创建函数组件
		function MyComponent(){
			return <h2>我是用函数定义的组件(用于简单组件】的定义)</h2>
		}
		//2.渲染组件到页面
		ReactDOM.render(<MyComponent/>,document.getElementById('test'))
	</script>

注意事项

本例中,执行ReactDOM.render(<MyComponent/>.......)之后,发生了什么

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

2.1.2 类式组件

用于复杂组件(有state

<script type="text/babel">
		//1.创建类式组件
		class MyComponent extends React.Component {
			render(){
				return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
			}
		}
		//2.渲染组件到页面
		ReactDOM.render(<MyComponent/>,document.getElementById('test'))
		
	</script>

注意事项

本例中,执行ReactDOM.render(<MyComponent/>.......)之后,发生了什么

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

2.2 组件实例的三大属性

针对于类式组件,hooks可以让函数式组件也有三大属性

2.2.1 state属性

代码实例: 点击切换天气状态

标准写法

<script type="text/babel">
		//1.创建组件
		class Weather extends React.Component{
			
			//构造调用几次? ———— 1次,只写了一个weather标签
			constructor(props){  //为了操作state
				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   //这种写法的依据?
				//复杂方法
				// const isHot = this.state.isHot;
				// const wind = this.state.wind;
				return <h1 onClick={this.changeWeather}>今天天气{isHot ? '炎热' : '凉爽'}{wind}</h1>
			}
			//、这里onclick赋值语句,不能调用
			//changeWeather调用几次? ———— 点几次调几次
			changeWeather(){
				//changeWeather放在哪里? ———— Weather的原型对象上,供实例使用
				//由于changeWeather是作为onClick回调,所以不是通过实例调用的,是直接调用
				//类中的方法默认开启局部严格模式,所以changeWeather中的this为undefined
				
				console.log('changeWeather');
				//获取原来的(isHot值
				const isHot = this.state.isHot
				//严重注意:状态必须通过setState进行更新,且更新是一种合并,不是替换(wind还在)。
				this.setState({isHot:!isHot})
				console.log(this);

				//严重注意:状态(state)不可直接更改,下面这行就是直接更改!!!
				//this.state.isHot = !isHot //这是错误写法
			}
		}
		//2.渲染组件到页面
		ReactDOM.render(<Weather/>,document.getElementById('test'))
				
	</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从原型上移到实例对象自身,外层函数的this就是箭头函数的this
			changeWeather = ()=>{
				const isHot = this.state.isHot
				this.setState({isHot:!isHot})
			}
		}
		//2.渲染组件到页面
		ReactDOM.render(<Weather/>,document.getElementById('test'))
				

注意事项

  • 类里可以直接写赋值语句,相当于追加属性(值写死),所以state可以直接在类里通过赋值的形式初始化,而不是在构造函数
  • 自定义回调函数的指向可以通过将箭头函数赋值的方式,从原型上转移到实例对象自身,箭头函数中this指向外层函数中this这里即为实例对象

强烈注意:

  1. 组件中render方法中的this为组件实例对象
  2. 组件自定义的方法中thisundefined如何解决
    a) 强制绑定this: 通过函数对象的bind()
    (可在constructor中用bind,也可在绑定事件函数时用bind,即onClick={this.changeWeather。bind(this)
    b) 箭头函数
  3. 状态数据,不能直接修改更新

2.2.2 props属性

代码实例: 自定义用来显示一个人员信息的组件

基本使用

<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="男" speak={speak}/>,document.getElementById('test1'))
		ReactDOM.render(<Person name="tom" age={18} sex="女"/>,document.getElementById('test2'))

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

注意事项

在 react 组件挂载之前,会调用它的构造函数。在为 React.Component 子类实现构造函数时,应在其他语句之前调用super(props)。否则,this.props构造函数中可能会出现未定义的 bug

通常,在 react 中,构造函数仅用于以下两种情况: a)通过给 this.state 赋值对象来初始化内部 state
b)为事件处理函数绑定实例

props进行限制

首先要引入prop-types,用于对组件标签属性进行限制

限制props有两种方法:
a)限制内容写在类外面

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
}

b)限制内容写在类里面

static propTypes = {
				name:PropTypes.string.isRequired, //限制name必传,且为字符串
				sex:PropTypes.string,//限制sex字符串
				age:PropTypes.number,//限制age为数值
			}

			//指定默认标签属性值
static defaultProps = {
	sex:'男',//sex默认值为男
	age:18 //age默认值为18
}

注意事项:

数组件使用props

三大属性中,只有props可以用于函数组件,因为函数可以接收参数staterefs都不能用于函数组件。

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

注意事项:

  • 限制props只能使用第一种方法

2.2.3 refs属性

组件内的标签可以定义ref属性来标识自己

this.refs拿到真实DOM
字符串形式的ref

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

不被官方推荐,因为效率不高

回调ref

1. 内联函数(推荐

<input ref={currentNode => this.input1 = currentNode } type="text" placeholder="点击按钮提示数据"/>&amp;nbsp; 
{/*这里的this是指向实例对象,因为箭头函数没有指向,查找外侧的this指向*/}

注意:

回调函数:有定义、没有执行(函数名())、最终执行(别人调用)

2. 类绑定函数

saveInput = (c)=>{
				this.input1 = c;
				console.log('@',c);
			}
<input ref={this.saveInput} type="text"/>

3. 回调ref中回调执行次数
内联函数更新时会执行两次一次清空一次执行函数,类绑定函数不会。

交互更改状态的区别:取决于是否修改render数中节点内容

createRef(react最推荐

<script type="text/babel">
		class Demo extends React.Component{
			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>

2.2.4 事件处理

  1. 通过onXxx属性指定事件处理函数(注意大小写)
    a)React使用的是自定义(合成)事件, 而不是使用的原生DOM事件——为了更好兼容
    b)React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)———为了高效
  2. 通过event.target得到发生事件的DOM元素对象。发生事件的元素就是操作的元素则可以省略ref。
  3. 不要过度使用ref

2.3 受控组件和非受控组件

表单提交案例为例

2.3.1 非受控组件

ref实现

页面中所有的输入类DOM现用现取,即通过ref标识DOM,进而获取数据

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

知识点

  1. 表单<form>中都有onSubmit属性来控制提交之后的状态
  2. 输入DOM(如<input>)得有name属性才能通过GET请求获取query参数(用?携带)
  3. 删掉action无法阻止表单页面刷新以及地址栏更新,得要禁止默认事件event.preventDefault()
  4. <button>默认type属性值就是submit

2.3.2 受控组件

onChange+state实现
页面中所有的输入类DOM将数据存在state

推荐用受控组件,减少ref的使用

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

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

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

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

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

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

2.4.1 高阶函数

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

  1. 若A函数,接收参数是一个函数,那么A就可以称之为高阶函数。
  2. 若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
    常见高阶函数有:PromisesetTimeoutarr.map()等等

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

function sum(a){
	return(b)=>{
		return (c)=>{
			return a+b+c
		}
	}
}

用函数的柯里化实现受控组件
为了不重复编写相似的代码,如saveUsernamesavePassword

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

			//保存表单数据到状态中(函数的柯里化
			saveFormData = (dataType)=>{
				return (event)=>{
					this.setState({[dataType]:event.target.value})
				}
			}

			//表单提交的回调
			handleSubmit = (event)=>{
				event.preventDefault() //阻止表单提交
				const {username,password} = this.state
				alert(`输入的用户名是:${username},你输入的密码是:${password}`)
			}
			render(){
				return(
					<form onSubmit={this.handleSubmit}>
						用户名:<input onChange={this.saveFormData('username')} type="text" name="username"/>
						密码:<input onChange={this.saveFormData('password')} type="password" name="password"/>
						<button>登录</button>
					</form>
				)
			}
		}
		//渲染组件
		ReactDOM.render(<Login/>,document.getElementById('test'))
	</script>
  • this.saveFormData('username'),有了小括号,立即调用,但是返回的还是一个函数,符合回调函数的要求
  • [dataType]:调用变量形式的对象属性名
  • event形参不需要实参,可以直接调用,所以event不能写进this.saveFormData('username')参数中,得用柯里化形式来体现
    不用函数柯里化的实现方式
    只需改两处:
saveFormData = (dataType,event)=>{
				this.setState({[dataType]:event.target.value})
			}
用户名:<input onChange={event => this.saveFormData('username',event) } type="text" name="username"/>

两种方法都常用

2.5 生命周期

2.5.1 引入

  1. 组件从创建到死亡它会经历一些特定的阶段
  2. React组件中包含系列钩子函数(生命周期回调函数), 会在特定的时刻调用。
  3. 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作
  4. 生命周期回调函数<=> 生命周期钩子函数<=>生命周期函数<=>生命周期钩子
  5. 挂载 mount: 组件第一次被渲染到DOM中的时候,就为其设置一个计时器,叫挂载 ReactDOM.render(<Life/>, document.querySelector('test'))
  6. 卸载 unmount:组件被删除时,应该清除计时器,在react中被称为卸载 ReactDOM.unmountComponentAtNode(document.querySelector('test'))

知识点

2.5.2 react生命周期(旧)

在这里插入图片描述
三条线:

  1. 父组件render–>componentWillReceiveProps–>shouldComponentUpdate–>componentWillUpdate–>render–>componentDidUpdate–>componentWillUnmount
  2. setState()–>shouldComponentUpdate–>componentWillUpdate–>render–>componentDidUpdate–>componentWillUnmount
  3. forceUpdate()–>componentWillUpdate–>render–>componentDidUpdate–>componentWillUnmount

知识点

生命周期的三个阶段(旧)

  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()常用,一般在这个钩子中做一些收尾的事,如关闭定时器取消订阅消息

2.5.3 react生命周期(新)

在这里插入图片描述

  • 17及以上版本componentWillMountcomponentWillUpdatecomponentWillReceiveProps三个钩子使用前要加UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用。
  • 新的生命周期和旧的生命周期相比,除了即将废弃三个钩子,还添加两个新的钩子getDerivedStateFromPropsgetSnapshotBeforeUpdate
  • static getDerivedStateFromProps:适用于罕见用例几乎不用),返回null或state对象,state的值在任何时候都取决于props
  • getSnapshotBeforeUpdate(prevProps,prevState,):在更新之前获取快照返回值传递componentDidUpdate(prevProps,prevState,snapshotValue)

2.6 虚拟DOM与DOM Diffing算法

DOM Diffing算法对比的最小粒度是标签,且逐层对比
在这里插入图片描述

2.6.1 key的作用

经典面试题

  1. react/vue中的key什么作用?(key的内部原理什么?)
  2. 为什么遍历列表时,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(如input,虚拟DOM中标签属性少,input没有value属性):
    会产生错误DOM更新 ==> 界面问题

  3. 注意! 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。

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

第3章 react应用(基于react脚手架

使用脚手架开发的项目模块化组件化工程化

3.1 前期准备

3.1.1 安装node.jsnpm

安装教程

  1. 下载.msi安装包 node官网
  2. 安装路径: D:nodejs
  3. 查看是否安装成功:win+r输入cmd,输入命令node查看npm版本 npm -v。node.js安装时会自动安装npm
  4. 改变npm下载的模块包的默认存储地址
    npm config set prefix “D:nodejsnode_global
    更改默认缓存位置npm config set cache “D:nodejsnode_cache
    测试npm listglobal或者npm config get prefix或者npm config get cache
  5. 更改环境变量
    控制面板系统高级系统设置高级环境变量
    1) 用户变量 path 中把 C:UsersAdministratorAppDataRoamingnpm;—改为—-D:nodejsnode_global
    2)在系统变量中 新增变量NODE_PATH—– D:nodejsnode_globalnode_modules

安装cnpm npm install -g cnpmregistry=https://registry.npm.taobao.org
安装yarn 不推荐用npm i -g yarn 安装(但我是这样装的)
react扩展程序React Developer Toolsfacebook

问题:

  1. 安装了cnpm之后,才成功安装了 cnpm i -g create-react-app
  2. cmd必须得以管理员模式运行才能使用npm命令解决方式是将vscode管理员模式运行,并用vscode运行npm命令

全局安装react脚手架 npm i -g create-react-app (不成功),得用 cnpm i -g create-react-app
创建项目 切换到想创建项目的文件夹,使用命令create-react-app app_name

换源这里建议把npm换源,使用国内镜像创建项目。因为使用脚手架配置项目的时候需要下载的依赖实在太多,不换源的话很可能中途卡住就失败了。(没换)
换源方式 npm config set registry https://registry.npm.taobao.org
验证是否换源成功:npm config get registry
显示出上述地址的话就是换源成功了
npm cnpm npx nvm区别

知识点

3.1.2 react脚手架项目结构

注意:
组件js文件除了首字母大写,还可以将后缀改为jsx,来和普通js文件进行区分

3.1.3 样式模块化

import name from './name.module.css'
className={name.title}

基本上不用,而是用less嵌套

遇到的问题:

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

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

3.2 案例:组件的组合使用——TodoList

3.2.1 注意事项

todos.map(todo=>{
	return <Item key={todo.id} {...todo}/>
	})

其中 {todos}=this.props ,是 todos解构对象成数组,即 todos=[{},{}],todo代表todos数组中的每个元素(对象)。

{...todo}是在展开对象。原生js中,对象不能使用拓展运算符(数组和可遍历的伪数组可以)。而{...obj}是ES6语法,是一个复制对象。但是在这里,{...todo}并不是复制对象,因为这里的{}表示括号里面要写js表达式了,所以真正写的还是...todo,这里react+babel就允许用展开运算符展开对象,不能随便使用(不能用console.log()查看),仅仅适用于标签传递数据时。

  • 父组件向子组件传数据:直接在子组件标签中添加键值对形式的数据(props
  • 子组件向父组件传递数据:父组件通过props给子组件传递一个函数,子组件在想要传递数据给父组件时,调用该函数
  • 生成时间戳:Date.now()
  • UUID:生成唯一时间戳的库
    npm i uuid (库比较大)
    npm i nanoid(库小,很快安装)/ yarn add nanoid
    用法
import {nanoid} from 'nanoid'
//调用
nanoid()

3.2.2 todoList案例相关知识点

  1. 分组件、实现静态组件,注意:classNamestyle的写法
  2. 动态初始化列表,如何确定将数据放在哪个组件的state中?
    1)某个组件使用:放在其自身的state
    2)某些组件使用:放在他们共同的父组件state中(官方称此操作为:状态提升)
  3. 关于父子之间通信
    1)【父组件】给【子组件】传递数据:通过props传递
    2)【子组件】给【父组件】传递数据:通过props传递,要求父提前给子传递一个函数
  4. 注意defaultCheckedchecked区别,类似的还有:defaultValuevalue
  5. 状态在哪里,操作状态的方法就在哪里

第4章 React ajax

4.1 理解

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

4.2 实现

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

4.3 配置代理

react脚手架配置代理总结

方法一

package.json追加如下配置

"proxy":"http://localhost:5000"

说明

  1. 优点:配置简单前端请求资源时可以不加任何前缀
  2. 缺点:不能配置多个代理
  3. 工作方式:上述方式配置代理,当请求了3000不存在资源时,那么该请求会转发给5000 (优先匹配前端资源)

方法二

  1. 一步:创建代理配置文件

    src下创建配置文件src/setupProxy.js(不允许改名)
    
  2. 编写setupProxy.js配置具体代理规则

    const proxy = require('http-proxy-middleware')
    
    module.exports = function(app) {
      app.use(
        proxy('/api1', {  //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
          target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址)
          changeOrigin: true, //控制服务器接收到的请求头中host字段的值
          /*
          	changeOrigin设置true时,服务器收到的请求头中的host为:localhost:5000
          	changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
          	changeOrigin默认值为false,但我们一般将changeOrigin值设为true
          */
          pathRewrite: {'^/api1': ''} //去除请求前缀/api1,保证交给后台服务器的是正常请求地址(必须配置)
        }),
        proxy('/api2', { 
          target: 'http://localhost:5001',
          changeOrigin: true,
          pathRewrite: {'^/api2': ''}
        })
      )
    }
    

说明

  1. 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
  2. 缺点:配置繁琐,前端请求资源时必须加前缀

学习原生AJAX时,解决跨域问题是在server.js代码中加入以下代码(即在后端解决

  //设置响应设置允许跨域  
  response.setHeader('Access-Control-Allow-Origin', '*');
  //响应
  response.setHeader('Access-Control-Allow-Headers', '*');

为CORS解决方案

注意:127.0.0.1和localhost的区别

  1. 127.0.0.1 为保留地址,直白地说 127.0.0.1 就是一个 ip 地址,不同于其它 ip 地址的是它是一个指向本机ip 地址,也称为环回地址,该 ip 地址不能使用在公网 ip 上,对任何一台电脑来说,不管是否连接到INTERNET上,127.0.0.1 都是指向自己
    事实上整个 127.* 网段都是环回地址,127.* ip 段都为保留地址,只是规则制定者将其中的 127.0.0.1 规定为指向本机自己

  2. 电脑网络中,localhost 为“本地主机”,是给回路网络接口loopback)的一个标准主机名,IPv4 相对应的地址为 127.0.0.1,IPv6 相对应的地址为 [::1],这个名称也是一个保留域名
    说白了就是 localhost 就是一个本地域名,不是地址。该本地域名指向的 ip 地址就是 127.0.0.1 ,也就是指向本机,localhost 更方便记忆与输入,因 hosts文件 定义了 localhost = 127.0.0.1 ,所以你只需要记住 localhost 就可以代表本机了。

  3. 机器来说,它需要通过 hosts 文件来定义 localhost = 127.0.0.1(多一次解析 ip 的步骤) ,才能知道 localhost 代表的是本机,机器知道 127.0.0.1 代表本机,因此当你向 localhost 发消息的时候,机器自动翻译后给127.0.0.1发消息

  4. 在实际工作中,localhost 是不经过网卡传输的,所以它不受网络防火墙和与网卡相关的种种限制,而 127.0.0.1 则要通过网卡传输数据,是必须依赖网卡的。这一点也是 localhost 和 127.0.0.1 的最大区别,这就是为什么有时候用 localhost 可以访问,但用 127.0.0.1 就不可以的情况。

4.4 案例github用户搜索

4.4.1 axios发送请求

  1. 连续解构赋值:
const {keyWordElement:{value}} = this

相当于 this.keyWordElement.value,此时keyWordElement并没有被解构,只是写的过程,即

console.log(keyWordElement) // undefined
const {keyWordElement:{value:keyWord}} = this

连续解构赋值+重命名

  1. 状态中的数据驱动着页面的展示
  2. 连续写三元表达式
isFirst ? <h2>欢迎使用,输入关键字,随后点击搜索</h2> :
isLoading ? <h2>Loading......</h2> :
err ? <h2 style={{color:'red'}}>{err}</h2> 

如果第一个判断正确,就不会执行后面的代码了,所以顺序很重要

4.4.2 消息订阅发布机制pubsub-js)

使用:

  1. 工具库: PubSubJS
  2. 下载: npm install pubsub-js –save
  3. 使用:
    1) import PubSub from ‘pubsub-js’ //引入
    2) PubSub.subscribe(‘delete’, function(data){ }); //订阅
    3) PubSub.publish(‘delete’, data) //发布消息

注意:
4. 适用于任意组件之前的消息沟通
5. 谁用谁接,谁传谁发

4.4.3 扩展:使用Fetch发送网络请求(用得不多)

  • 发送AJAX请求的方法:
  1. 原生AJAX:xhr
  2. jQuery:对xhr的封装
  3. axios:对xhr的封装
  4. fetch:不是库,是windows内置,且是Promise风格xhr并列

4.4 案例总结

  1. 设计状态时要考虑全面,例如带有网络请求的组件,要考虑请求失败怎么办。
  2. ES6小知识点:解构赋值+重命名
    let obj = {a:{b:1}}
    const {a} = obj; //传统解构赋值
    const {a:{b}} = obj; //连续解构赋值
    const {a:{b:value}} = obj; //连续解构赋值+重命名
  3. 消息订阅与发布机制
    1.先订阅,再发布(理解:有一种隔空对话的感觉)
    2.适用于任意组件间通信
    3.要在组件的componentWillUnmount中取消订阅
  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);
	}

第5章 react路由(v5)

5.1. 相关理解

5.1.1. SPA的理解

  1. 单页Web应用(single page web application,SPA)。
  2. 整个应用只有一个完整的页面。
  3. 点击页面中的链接不会刷新页面,只会做页面的局部更新
  4. 数据都需要通过ajax请求获取, 并在前端异步展现。
  5. 单页面、多组件

5.1.2. 路由的理解

  1. 一个路由就是一个映射关系(key:value)
  2. key为路径, value可能是function或component
  1. 后端路由
    1) 理解: value是function, 用来处理客户端提交的请求。
    2) 注册路由: router.get(path, function(req, res))
    3) 工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
  2. 前端路由:
    1) 浏览器端路由,value是component,用于展示页面内容。
    2) 注册路由: <Route path="/test" component={Test}>
    3) 工作过程:当浏览器path变为/test时, 当前路由组件就会变为Test组件
    4)前端路由也要靠BOM 上的 history

5.1.3. react-routerdom的理解

  1. react的一个插件库
  2. 专门用来实现一个SPA应用。
  3. 基于react的项目基本都会用到此库。

**注意:**react-router总共有三种形式的库,适用于三种场景

5.2 路由的基本使用

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

  2. 导航区的a标签改为Link标签
    Demo

  3. 展示区写Route标签进行路径的匹配(在呈现路由组件内容的位置注册路由)
    以前的版本

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

    现在的版本 6.x.x

    <Routes>
    	<Route path="/about" element={<About/>}/>
    	<Route path="/home" element={<Home/>}/>
    </Routes>
    
  4. <App/>的最外侧包裹了一个<BrowserRouter><HashRouter>

注意:

5.3 路由组件与一般组件

  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"

5.4 NavLink及其封装

  1. NavLink可以实现路由链接高亮,通过activeClassName指定样式名
  2. 标签体内容是一个特殊的标签属性,可以通过this.props.children获取。因此以下两段代码是等价的。
<NavLink activeClassName="atguigu" className="list-group-item" children="About" />
<NavLink activeClassName="atguigu" className="list-group-item" to="/about">About</NavLink>

5.5 Switch

  1. 注册路由时用Switch包裹所有路由
  2. 通常情况下,path和component是一一对应的关系
  3. Switch可以提高路由匹配效率(单一匹配)。

5.6 解决样式丢失问题

  1. 什么时候样式丢失
    路由路径多级,且刷新的时候

  2. 解决办法
    1)public/index.html 中 引入样式时不写 ./ 而是写 / (常用)。因为./是相对路径,去掉之后就是绝对路径,直接去localhost:3000下调文件

    <link rel="stylesheet" href="/css/bootstrap.css">
    

    2)public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)。因为%PUBLIC_URL%代表public的绝对路径

    <link rel="stylesheet" href="%PUBLIC_URL%/css/bootstrap.css">
    

    3)将<BrowserRouter>改为<HashRouter>

5.7 路由的模糊匹配与严格匹配

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

    <MyNavLink to="/about/a/b">Home</MyNavLink>  //模糊匹配
    <Route path="/about" component={About}/>
    
  2. 开启严格匹配:

<Route exact={true} path="/about" component={About}/>
// exact={true}可以简写为exact
  1. 严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由

5.8 Redirect重定向

  1. 一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
  2. 具体编码:
<Switch>
	<Route path="/about" component={About}/>
	<Route path="/home" component={Home}/>
	<Redirect to="/about"/>
</Switch>

5.8 嵌套路由(多级路由)

  1. 注册子路由时要写上父路由的path值
{/* 注册路由 */}
<Switch>
	<Route path="/home/news" component={News}/>
	<Route path="/home/message" component={Message}/>
	<Redirect to="/home/news"/>
</Switch>
  1. 路由的匹配是按照注册路由的顺序进行的

5.9 向路由组件传递参数

5.9.1 params参数(最多)

  1. 路由链接(携带参数):
<Link to='/demo/test/tom/18'}>详情</Link>
  1. 注册路由(声明接收):
<Route path="/demo/test/:name/:age" component={Test}/>
  1. 接收参数:this.props.match.params

5.9.2 search参数

  1. 路由链接(携带参数):
<Link to='/demo/test/?name=tom&age=18'}>详情</Link>
//?前面加不加/
  1. 注册路由(无需声明,正常注册即可):
<Route path="/demo/test" component={Test}/>
  1. 接收参数:this.props.location.search
    备注:获取到的search是urlencoded编码(即,name=tom&age=18字符串,需要借助querystring解析querystring.stringify(obj), querystring.parse(str))。去掉问号用qs.parse(str.slice(1)

5.9.3 state参数(不同于普通组件的state属性)

  1. 路由链接(携带参数):
<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>
  1. 注册路由(无需声明,正常注册即可):
<Route path="/demo/test" component={Test}/>
  1. 接收参数:this.props.location.state
    备注刷新也可以保留住参数,但回退不行,无法回退到上一个·同级路由**????**(老师演示的时候可以回退自己跑代码回退不了)

5.10 push和replace(难)

  1. 路由是对浏览器历史记录的操作,总共有两种操作,push(压栈)和replace(替代栈顶元素)。
  2. 默认是push模式,要想开启replace模式,则在路由连接<Link>标签中加入replace={true}replace

5.11 编程式路由导航

借助this.prosp.history对象上的API对操作路由跳转、前进、后退,而不用路由的<Link><NavLink>,但还是要注册路由

5.12 withRouter的使用

  1. withRouter可以加工一般组件,让一般组件具备路由组件所特有的API

  2. withRouter返回值是一个新组件。

  3. 在一般组件中要用到路由组件的props属性时引入。

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

    需要暴露

    export default withRouter(Header)
    

5.12 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可以用于解决一些路径错误相关的问题。

第6章 React UI组件库

6.1 简介

material-ui(国外)

  1. 官网: http://www.material-ui.com/#/
  2. github: https://github.com/callemall/material-ui

antdesign(国内蚂蚁金服)

  1. 官网: https://ant.design/index-cn
  2. Github: https://github.com/antdesign/ant-design/
  3. 不是所有场景都使用,最适用于后台管理系统

6.2 使用

安装依赖:npm install antd
引入样式(不太合理,引入了全部组件的样式,应该按需引入

import '../node_modules/antd/dist/antd.css'

引入库:

import { Button,DatePicker } from 'antd';
import {WechatOutlined,WeiboOutlined,SearchOutlined} from '@ant-design/icons'

学会查看官网文档
其他UI组件库:element ui、vant等

6.3 按需引入和自定义主题

重点: 学会查看文档(3.x文档更清楚,且适用于4.x)

  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"
},
  1. 根目录下创建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' },
		}
	}),
);
  1. 备注:不用在组件里亲自引入样式了,即:import 'antd/dist/antd.css'应该删掉

第7章 redux

7.1 redux理解

7.1.1 学习文档

  1. 英文文档: https://redux.js.org/
  2. 中文文档: http://www.redux.org.cn/
  3. Github: https://github.com/reactjs/redux

7.1.2 redux简介

  1. redux是一个专门用于做状态管理的JS库(不是react插件库)。
  2. 它可以用在react, angular, vue等项目中, 但基本与react配合使用。
  3. 作用: 中式管理react应用中多个组件共享的状态。

7.1.3 什么情况下需要使用redux

  1. 某个组件的状态,需要让其他组件可以随时拿到(共享)。
  2. 一个组件需要改变另一个组件的状态(通信)。
  3. 总体原则:能不用就不用, 如果不用比较吃力才考虑使用。

7.1.4 redux工作流程

在这里插入图片描述

7.2 redux的三个核心概念

7.2.1. action

  1. 动作对象
  2. 包含2个属性
    type:标识属性, 值为字符串, 唯一, 必要属性
    data:数据属性, 值类型任意, 可选属性
  3. 例子{ type: 'ADD_STUDENT',data:{name: 'tom',age:18} }

7.2.2. reducer

  1. 用于初始化状态、加工状态。
  2. 加工时,根据旧的state和action, 产生新的state的纯函数
  3. 有几个组件就有几个reducer?

7.2.3. store

  1. 将state、action、reducer联系在一起的对象

  2. 如何得到此对象?
    1) import {createStore} from 'redux'
    2) import reducer from './reducers'
    3) const store = createStore(reducer)

  3. 此对象的功能?
    1) getState(): 得到state
    2) dispatch(action): 分发action, 触发reducer调用, 产生新的state
    3) subscribe(listener): 注册监听, 当产生了新的state时, 自动调用

7.3. redux的核心API

7.3.1. createstore()

作用:创建包含指定reducer的store对象

7.3.2. store对象

  1. 作用: redux库最核心管理对象

  2. 它内部维护着:
    1) state
    2) reducer

  3. 核心方法:
    1) getState()
    2) dispatch(action)
    3) subscribe(listener)store监测redux里的状态变化,发生变化则会被调用

  4. 具体编码:
    1) store.getState()
    2) store.dispatch({type:'INCREMENT', number})
    3) 放在入口文件index.js中

    store.subscribe(()=>{
    	ReactDOM.render(<App/>,document.getElementById('root'))
    })
    

7.3.3. applyMiddleware()

作用:应用上基于redux的中间件(插件库)

7.3.4. combineReducers()

作用:合并多个reducer函数

7.4 求和案例

7.4.1 求和案例_redux精简版

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

  2. src下建立:

    //文件
    -redux
    	-store.js
    	-count_reducer.js
    
  3. store.js:
    1)引入redux中的createStore函数,创建一个store
    2)createStore调用时要传入一个为其服务的reducer
    3)记得暴露store对象(store.js默认暴露一个函数调用,函数返回一个对象,其他文件引入的时候将此对象命名为store)

  4. count_reducer.js:
    1)reducer的本质是一个函数,接收:preState,action,返回加工后的状态
    2)reducer有两个作用:初始化状态,加工状态
    3)reducer被第一次调用时,是store自动触发的。传递的preStateundefined,传递的action是:{type:'@@REDUX/INIT_a.2.b.4}

  5. 在index.js中监测store中状态的改变,一旦发生改变重新渲染

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

7.4.2 求和案例_redux完整版

新增文件

  1. count_action.js 专门用于创建action对象
    注意:箭头函数返回一个对象时,要用()包裹,不然会被识别为函数体的{}
createIncrementAction = data => ({type:INCREMENT,data})
  1. constant.js 放置容易写错的type值,目的只有一个:便于管理的同时防止程序员单词写错(要暴露)

7.4.3 求和案例_redux异步action版(非必须)

  1. 同步actionaction的值是对象
    异步actionaction的值是函数。异步action一般会调用同步action
  2. 明确:延迟动作异步操作)不想交给组件自身,想交给action
  3. 何时需要异步action:想要对状态进行操作,但是具体的数据靠异步任务返回。
  4. 具体编码:
    1)yarn add redux-thunk,并配置在store中
    2)创建action的函数不再返回一般对象,而是一个函数,该函数中写异步任务
    3)异步任务结果后,分发一个同步action去真正操作数据。
  5. 备注:异步action不是必须要写的,完全可以自己等待异步任务结果了再去分发同步action

7.4.4 求和案例_react-redux基本使用

在这里插入图片描述

  1. 明确两个概念
    1)UI组件:不能使用任何redux的api,只负责页面的呈现、交互等。
    2)容器组件:负责和redux通信,将结果交给UI组件。

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

    connect(mapStateToProps,mapDispatchToProps)(UI组件)
    

    1)mapStateToProps:映射状态,返回值是一个对象。返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
    2)mapDispatchToProps:映射操作状态的方法,返回值是一个对象。返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value

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

7.4.5 求和案例_react-redux优化

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

  2. 若有多个容器组件,无需自己每个容器组件传递store,给包裹一个<Provider store={store}>即可

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

  4. mapDispatchToProps也可以简单的写成一个对象,因为react-redux可以自动dispatch

  5. 一个组件要和react-redux“打交道”要经过哪几步?
    1)定义好UI组件—不暴露
    2)引入connect生成一个容器组件,并暴露,写法如下:

    connect(
    	state => ({key:value}), //映射状态
    	{key:xxxxxAction} //映射操作状态的方法
    )(UI组件)
    

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

7.4.6 求和案例_react-redux数据共享

  1. 定义一个Person组件,和Count组件通过redux共享数据。
  2. 为Person组件编写:reducer、action,配置constant常量
  3. 重点:Person的reducer和Count的Reducer要使用combineReducers进行合并。合并后的总状态是一个对象!!!
  4. 交给store的是总reducer,最后注意在组件中取出状态的时候,记得“取到位”。
  5. reducers进行浅比较,数组、对象只比较地址,所以地址不变就不会重新渲染

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

Redux DevTools
在这里插入图片描述

  1. 引入库
yarn add redux-devtools-extension
  1. 在store.js中进行配置
import {composeWithDevTools} from 'redux-devtools-extension'
export default createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))

7.4.8 求和案例_react-redux最终版

  1. 所有变量名字要规范,尽量触发对象的简写形式。
  2. reducers文件夹中,编写index.js专门用于汇总并暴露所有的reducer

7.5 纯函数和高阶函数

7.5.1. 纯函数

  1. 一类特别的函数: 只要是同样的输入(实参),必定得到同样的输出(返回)
  2. 必须遵守以下一些约束
    1) 不得改写参数数据
    2) 不会产生任何副作用,例如网络请求,输入和输出设备
    3) 不能调用Date.now()或者Math.random()等不纯的方法
  3. redux的reducer函数必须是一个纯函数

7.8.2. 高阶函数

  1. 理解: 一类特别的函数
    1) 情况1: 参数是函数
    2) 情况2: 返回是函数
  2. 常见的高阶函数:
    1) 定时设置函数
    2) 数组的forEach()/map()/filter()/reduce()/find()/bind()
    3) promise
    4) react-redux中的connect函数
  3. 作用: 能实现更加动态, 更加可扩展的功能

7.9 项目打包

  1. 打包

    npm run build
    

    生成build文件夹

  2. 部署服务器
    1)阿里线上服务器
    2)express(Node中快速搭建服务器框架搭建服务
    3)借助第三方库库快速搭建:以指定的文件夹作为根目录快速开启一台服务器。如 serve

    npm i serve -g  //全局安装
    serve build   // 打开生成的build文件夹
    

第8章 扩展

8.1 setState

setState更新状态的2种写法

  1. setState(stateChange, [callback])——对象式的setState
    1)stateChange为状态改变对象(该对象可以体现出状态的更改)
    2)callback是可选的回调函数, 它在状态更新完毕界面也更新后(render调用后)才被调用

  2. setState(updater, [callback])——函数式的setState
    1)updater为返回stateChange对象的函数。
    2)updater可以接收到stateprops
    3)callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。

总结:

  1. 对象式的setState是函数式的setState的简写方式(语法糖)
  2. 使用原则
    1)如果新状态不依赖于原状态 ===> 使用对象方
    2)如果新状态依赖于原状态 ===> 使用函数方式
    3)如果需要在setState()执行后获取最新的状态数据,
    要在第二个callback数中读取

8.2 lazyLoad

路由组件的lazyLoad

  1. 通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
  2. 通过指定在加载得到路由打包文件前显示一个自定义loading界面
	//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>

8.3 Hooks

8.3.1 React Hook/Hooks是什么?

  1. Hook是React 16.8.0版本增加的新特性/新语法
  2. 可以让你在函数组件中使用 state 以及其他的 React 特性

8.3.2 三个常用的Hook

  1. State Hook: React.useState()
  2. Effect Hook: React.useEffect()
  3. Ref Hook: React.useRef()

8.3.3 State Hook

  1. State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
  2. 语法:
const [xxx, setXxx] = React.useState(initValue)  
  1. useState()说明:
    参数: 第一次初始化指定的值在内部作缓存
    返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
  2. setXxx()的2种写法:
    setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
    setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值

8.3.4 Effect Hook

  1. Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
  2. React中的副作用操作:
    ajax请求数据获取
    设置订阅 / 启动定时
    手动更改真实DOM(尽量避免)
  3. 语法和说明:
useEffect(() => { 
  // 在此可以执行任何带副作用操作
  return () => { // 在组件卸载前执行
    // 在此做一些收尾工作, 比如清除定时器/取消订阅等
  }
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
  1. 可以把 useEffect Hook 看做如下三个函数的组合
    componentDidMount()
    componentDidUpdate()
    componentWillUnmount()

8.3.5 Ref Hook

  1. Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
  2. 语法:
const refContainer = useRef()  //声明
refContainer.current.xxx  //调用
  1. 作用: 保存标签对象,功能与React.createRef()一样

8.4 Fragment

使用

import {Fragment } from 'react'
//类似于一个<div>
<Fragment></Fragment>    //可以添加一个属性 'key',用于遍历
<></>  //不能添加任何属性

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

8.5 Context

8.5.1 理解

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

8.5.2 使用

  1. 创建Context容器对象:

    const XxxContext = React.createContext()  
    //const {Provider,Consumer} = XxxContext 也可以结构赋值
    
  2. 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据

    <xxxContext.Provider value={数据}>
    		子组件
        </xxxContext.Provider>
    
  3. 后代组件读取数据
    1) 第一种方式:仅适用于类组件

      static contextType = xxxContext  // 声明接收context
    	  this.context // 读取context中的value数据
    

    2)第二种方式: 函数组件与类组件都可以

     <xxxContext.Consumer>
    		 //组件中的函数
    	    {
    	      value => ( // value就是context中的value数据
    	        要显示的内容
    	      )
    	    }
    	  </xxxContext.Consumer>
    

注意: 在应用开发中一般不用context, 一般都用它的封装react插件(如,react-redux)

8.6 组件优化

Component的2个问题

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

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

效率高的做法

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

原因

Component中的shouldComponentUpdate()总是返回true

解决

  1. 办法1: 重写shouldComponentUpdate()方法
    比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false

    shouldComponentUpdate(nextProps,nextState){
    		// console.log(this.props,this.state); //目前的props和state
    		// console.log(nextProps,nextState); //接下要变化的目标props,目标state
    		return !this.state.carName === nextState.carName
    	}
    
  2. 办法2: 使用PureComponent
    PureComponent重写shouldComponentUpdate(), 只有state或props数据有变化才返回true

    import React, { PureComponent } from 'react'
    //之前是import React, { Component } from 'react'
    

注意:
1. PureComponent,只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false
2. 不要直接修改state数据, 而是要产生新数据(新对象)。所以不能用unshift
5. 项目中一般使用PureComponent优化


8.7 render props

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

  1. Vue中:
    使用slot插槽技术, 也就是通过组件标签体传入结构

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

8.7.2 children props

<A>
 </B>
</A>
	{this.props.children}

问题: 如果B组件需要A组件内的数据, ==> 做不到

8.7.3 render props

<A render={(data) => <C data={data} />} />
//相当于<A render={(data) => <C data={data}></C>}></A>

A组件: {this.props.render(内部state数据)}
C组件: 读取A组件传入的数据显示 {this.props.data}
注意: 只要组件不带内容,单标签和双标签实现效果相同(即<A></A>==<A/>

8.8 错误边界

8.8.1 理解

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

8.8.2 特点

  1. 只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
  2. 只适用于生产环境(build打包),而不是开发环境

8.8.3 使用方式

getDerivedStateFromError配合componentDidCatch

// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
    console.log(error);
    // 在render之前触发
    // 返回新的state
    return {
        hasError: true,
    };
}

componentDidCatch(error, info) {
    // 统计页面的错误。发送请求发送到后台去
    console.log(error, info);
}

8.9 组件通信方式总结

8.9.1 组件间的关系

  • 父子组件
  • 兄弟组件(非嵌套组件)
  • 祖孙组件(跨级组件)

8.9.2 几种通信方式:

  1. props:
    1)children props
    2)render props
  2. 消息订阅-发布:
    pubssub、event等等
  3. 中式管理
    redux、dva等等
  4. conText:
    生产者消费者模式

8.9.2 比较好的搭配方式:

  • 父子组件:props
  • 兄弟组件:消息订阅-发布、集中式管理
  • 祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)

第9章 React Router 6 快速上手

9.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).新增多个hookuseParamsuseNavigateuseMatch等。

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

9.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,下面是指定自定义的className
    
    //自定义样式
    <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>
    

    注意: 默认情况下,当Home的子组件匹配成功,Home的导航也会高亮,当NavLink上添加了end属性后,若Home的子组件匹配成功,则Home的导航没有高亮效果。

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

9.3 Hooks

1-6常用

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/>
    		children:[    //子路由
        		{  
        		path:'news',
        		element:<News/>
        		}
    		]
    	},
    	{
    		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)  //一级路由用useRoutes()和element定位,子路由用<outlet />定位
    	return (
    		<div>
    			......
          {/* 注册路由 */}
          {element}
    		  ......
    		</div>
    	)
    }
    
    

    注意: 一级路由(App.js中)用useRoutes()和element定位,子路由用<outlet />定位

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(search,setSearch)

  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属性。可以传递state参数

  2. 示例代码:

    import React from 'react'
    import {useLocation} from 'react-router-dom'
    
    export default function Detail() {
    const {state:{id,title,content}} = useLocation()
    	//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)
    // 如果嵌套路由没有挂载,则resultnull
    // 如果嵌套路由已经挂载,则展示嵌套的路由对象
    

10.useResolvedPath()

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

原文地址:https://blog.csdn.net/qq_44870156/article/details/122735033

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_25522.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注