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>hello-reacttitle>
head>
<body>
<div id="test">div>
body>
<script src="./js/react.development.js">script>
<script src="./js/react-dom.development.js">script>
<script src="./js/babel.min.js">script>
<script type="text/babel"> //这里的type要声明为"text/babel"
//创建一个虚拟DOM
const VDOM = <h1>hello</h1> //这里使用的是jsx
//渲染虚拟DOM
ReactDOM.render(VDOM,document.getElementById("test"))
script>
html>
//1、
const VDOM=(
<h1>你好呀h1>
)
//2、使用react中的函数createElement(标签名,属性,标签体内容)
const VDOM = react.createElement('h1',null,'你好呀')
jsx语法规则:
1:定义虚拟DOM时,不需要引号
2: 标签中混入js表达式时,需要用花括号{}
3:样式的类名的指定不要用class,要用className
4:内联样式要用 style = {{key:value}}的样式去写,注意想font-size这样的key要用小驼峰的格式去写fontSize
5: jsx中的VDOM只有一个根标签
6:标签必须闭合
7:关于标签首字母
(1).如果是小写字母开头,则将改标签和html中同名元素进行比对,若html中没有该标签同名元素,则报错
(2).如果是大写字母开头,react就去渲染对应的组件,若没有该组件,则标错
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>hello-reacttitle>
head>
<style>
.title{
background-color: skyblue;
}
style>
<body>
<div id="test">div>
body>
<script src="./js/react.development.js">script>
<script src="./js/react-dom.development.js">script>
<script src="./js/babel.min.js">script>
<script type="text/babel">
const myId = 'TEST'
const myDate = '你好呀'
//创建虚拟的DOM
const VDOM = (
<h2 className="title">
<span style={{color:'white'}} id = {myId}>
{myDate}
</span>
<input type="text"/>
</h2>
)
//渲染虚拟DOM到页面
ReactDOM.render(VDOM,document.getElementById("test"))
script>
html>
模块
(1)、理解向外提供特定功能的js程序,一般就是一个js文件
(2)、为什么要拆成模块:随着业务逻辑的增强,代码越来越多且越来越复杂
(3)、作用可以大大提高js代码的复用,提高js运行效率
组件
(1)、理解:用来实现局部功能效果的代码和资源的集合(html/css//js/image等等)
(2)、为什么:一个界面的功能更复杂
(3)、作用:复用编码,简化项目编码,提高运行效率
模块化
当应用的js都是一模块化来编写的,这个应用就是一个模块化的应用
组件化
当应用是以多组件的方式实现的,,这个应用就是一个组件化的应用 -->
一定注意区分:【js语句代码】与【js表达式】
1.表达式:表达式会产生一个值,可以放存放任何需要值的地方
(1). a
(2). d+b
(3). demo(1)
(4). arr.map()
(5). function()
2.语句(代码):
下面这些都是语句(代码)
(1). if(){}
(2). for(){}
…
注意:
(1)、创建函数组件时需要给函数名的首字母大写
(2)、在渲染函数组件时有些标签 <函数名/> 而不是只写函数名
(3)、渲染的写的标签要闭合
(4)、函数必须要有返回值
执行了ReactDOM.render(,document.get…
1、React解析组件标签,找到了MyComponent组件
2、发现组件时使用函数定义的,随后调用给函数,将返回的虚拟DOM转换成真实的DOM,随后呈现在页面中
function Mycomponent(){
return (
<h2>你好吗?h2>
)
}
ReactDOM.render(<Mycomponent/>,document.getElementById('root'))
注意:
1)、类式组件类名首字母需要大写
2)、类式组件需要继承component类
3)、类式组件必要包含一个函数render()
该函数必须有返回值
lass MyComponet extends React.Component {
render() {
return <h2>我是用类定义的组件(使用与【复杂组件】的定义)h2>
}
}
ReactDOM.render(<MyComponet/>, document.getElementById('test'))
注意:
(1)、类式组件在使用state时,需要启动构造函数,构造函数需要填写props,且要写super(props)
(2)、在state中存数据时,需要以对象key-value的形式进行存储
(3)、获取state里的数据时,this.state.key
(4)、在react中绑定事件和原生的js绑定事件方式一样,都可以使用三种方式
1、element.addEventLister(‘click’,()=>{})
2、element.onclick = ()=>{}
2、
但需要注意的是,在react中尽量使用第三种,不推荐是用前两种,因为在react中要尽量减少document的使用
(5)、修改state中的数据时
this.setSate({key:value})
class Weather extends React.Component{
state = {
isHot:true,
wind:'微风'
}
render(){
return <h2 onClick = {this.changeWeaher}>今天天气很{this.state.isHot?'炎热':'凉爽'},{this.state.wind}</h2>
}
changeWeaher = () =>{
this.setState({isHot:!this.state.isHot})
}
}
ReactDOM.render(<Weather/>,document.getElementById('test'))
1)、基本使用:
(1)、对象中的属性props是直接在收集标签里的属性和值的,
ReactDOM.render( ,document.getElementById(‘test’))
这里的props就自己收集标签中的name和sex两个属性和值
(2)、在标签中可以使用展开运算符,将外界的对象展开到标签中
const p = {name:“南山”,sex:‘男’}
ReactDOM.render(
2)、对props的内容限制:
(1)、在对标签中的属性进行限制时,只有对props中的属性进行限制
(2)、这里需要引用导包prop-types.js,在全局引入PropTypes这个对象
(3)、对prop中的属性就行限定:
类.propTypes = {
属性:PropTypes.string.isRequire **这里表示对该属性进行限定,只能是字符,且是不填的
}
Person.propTypes= {
name:PropTypes.string.isRequire,
speack:PropTypes.func **这里指定speack 为函数
}
(4)、但我们要设定默认属性时需要:
类.defaultProps = {
属性:value
}
Person.defaultProps = {
sex:“不男不女”
}
class Person extends React.Component{
render(){
const {name,age,sex} = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
<li>性别:{sex}</li>
</ul>
)
}
}
Person.propTypes ={ //propTypes属性规则
name:PropTypes.string.isRequired,
sex:PropTypes.string,
age:PropTypes.number
}
Person.defaultProps = {
sex:"不男不女"
}
function speack (){
console.log('说话了。。')
}
简写版:
class Person extends React.Component{
static propTypes ={ //propTypes属性规则
name:PropTypes.string.isRequired,
sex:PropTypes.string,
age:PropTypes.number
}
static defaultProps = {
sex:"不男不女"
}
render(){
const {name,age,sex} = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
<li>性别:{sex}</li>
</ul>
)
}
}
函数组件可以使用props
function Person(props){
const {name,sex,age} = {...props};
return (
<ul>
<li>姓名: {name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
//给函数组件本身添加propTypes限定
Person.propTypes = {
name:PropTypes.string.isRequire,
sex:PropTypes.string
}
//给函数组件本身添加一个默认值的defaultProps
Person.defaultProps={
sex:"不男不女"
}
//注意这里的渲染要 写在最后面
ReactDOM.render(<Person name="念念" age = {19}/>,document.getElementById('test3'))
ReactDOM.render(<Person name="1念" age = {19} sex= "男"/>,document.getElementById('test2'))
1)、refs-string形式:
直接书写字符串,refs自动收集
class Demo extends React.Component{
render(){
return(
)
}
btn=()=>{
const input1 = this.refs.input1;
alert(input1.value)
input1.value = ''
}
blur=()=>{
alert(this.refs.input2.value)
}
}
ReactDOM.render( ,document.getElementById('test'))
2)、ref回调函数的形式:
class Demo extends React.Component{
render(){
return(
<div className = "line">
<input type="text"ref="input1" placeholder ="点击按钮提示输入信息"/>
<button onClick = {this.btn}>点我提示左侧数据</button>
<input type="text" ref={e=>this.input1=e} onBlur={this.blur} placeholder ="失去焦点提示输入信息"/>
<input type="text" ref={thid.saveInput} onBlur={this.blur} placeholder ="失去焦点提示输入信息"/>
</div>
)
}
saveInput(e)=>{
this.input2=e;
}
btn=()=>{
const input1 = this.input1;
const input2 = this.input2;
alert(input1.value)
input1.value = ''
}
blur=()=>{
alert(this.refs.input2.value)
}
}
ReactDOM.render(<Demo/>,document.getElementById('test'))
3、使用createRef的形式:
class Demo extends React.Component{
/*
React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是专人专用的
*/
myRef = React.createRef()
render(){
return(
<div>
<input type="text"ref={this.myRef}placeholder ="点击按钮提示输入信息"/>
<button onClick = {this.btn}>点我提示左侧数据</button>
<input type="text" ref={e=>this.input2 = e} onBlur={this.blur} placeholder ="失去焦点提示输入信息"/>
</div>
)
}
btn=()=>{
const input1 = this.myRef.current;
alert(input1.value)
input1.value = ''
}
blur=()=>{
alert(this.input2.value)
}
}
ReactDOM.render(<Demo/>,document.getElementById('test'))
(1)、通过onXxx属性指定事件处理函数(注意大小写)
a、React使用的是自定义(合成)事件,而不是使用的原生DOM事件————为了更好的 兼容性
b、React中的事件时通过事件的委托来处理的(委托给组件最外成元素)
(2)、通过event.target得到发生事件的DOM元素对象
class Demo extends React.Component{
//创建ref容器
myRef1 = React.createRef();
render(){
//
return (
<div>
<input type="text" ref={this.myRef1} placeholder ="点击暗流出现体现信息"/>
<button onClick = {this.btn}>点击显示输入数据</button>
<input type="text" onBlur={this.show} placeholder="失去焦点发送提示信息"/>
</div>
)
}
btn=()=>{
const input1 = this.myRef1.current;
alert(input1.value)
}
show=(e)=>{
console.log(e.target.value)
console.log(e.target.parent)
}
}
ReactDOM.render(<Demo/>,document.getElementById('test'))
页面中的所有输入类的dom,随着输入,将获取的值放在state状态里面,这里就是受控组件,反之,则为非受控组件
高阶函数:
如果一个函数符合下面两个规范中的任何一个,那么函数就是高阶函数
(1)、若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数
(2)、若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数
函数的柯里化:
通过函数调用继续返回函数的方式,实现多次接收参数最后形成统一处理的函数编码形式。
class Region extends React.Component {
state = {
UseName: '',
pwd: '',
add:''
}
render() {
return (
<form action="" onSubmit={this.submit}>
用户名:<input onChange={this.saveMsg("UseName")} type="text" />
密 码:<input onChange={this.saveMsg("pwd")} type="password" />
地址:<input onChange={this.saveMsg("add")} type="text" />
<button onClick={this.btn}>提交</button>
</form>
)
}
btn = () => {
alert(`输入的用户名${this.state.UseName},输入的密码是${this.state.pwd}`)
}
onSubmit = (e) => {
e.preventDefault();
}
//saveMsg函数就是高阶函数,也是函数柯里化
saveMsg=(dataType)=>{
return (e)=>{
this.setState({
[dataType]:e.target.value
})
}
}
}
ReactDOM.render(<Region />, document.getElementById("test"));
class Region extends React.Component {
state = {
UseName: '',
pwd: '',
add:''
}
render() {
return (
<form action="" onSubmit={this.submit}>
用户名:<input onChange={e=>this.saveMsg("UseName",e)} type="text" />
密 码:<input onChange={e=>this.saveMsg("UseName",e)} type="password" />
地址:<input onChange={e=>this.saveMsg("UseName",e)} type="text" />
<button onClick={this.btn}>提交</button>
</form>
)
}
btn = () => {
alert(`输入的用户名${this.state.UseName},输入的密码是${this.state.pwd}`)
}
onSubmit = (e) => {
e.preventDefault();
}
saveMsg=(dataType,e)=>{
this.setState({
[dataType]:e.target.value
})
}
}
ReactDOM.render(<Region />, document.getElementById("test"));
声明周期回调函数<=>生命周期钩子函数<=>生命周期函数<=>生命周期钩子
注意:
1、初始化阶段:由ReactDOM.render()触发—初次渲染
(1)、constructor()
(2)、componentWillMount()
(3)、render()
(4)、componentDidMount()
2、更新阶段:由组件内部的this.setState()或者父组件render触发
(1)、shouldComponentUpdate()
(2)、componentWillUpdate()
(3)、render()
(4)、componentDidUpdate()
3、卸载组件:由ReactDOM.unmountComponentAtNode()触发
(1)、componentWillUnmount()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vvj2mTk1-1630217723706)(D:\百度下载\尚硅谷前端学科全套教程(总126.90GB)\2.尚硅谷前端学科–高级技术\尚硅谷React全家桶教程\react全家桶资料\02_原理图\react生命周期(旧)].png)
1、初始化阶段:由ReactDOM.render()触发—初次渲染
(1)、constructor()
(2)、getDerivedStateFromProps()
(3)、render()
(4)、componentDidMount()
一般在这个钩子中做一些初始化的事,列如:开启定时器,发送网络请求,订阅消息
2、更新阶段:由组件内部this.setState()或父组件重新render触发
(1)、getDerivedStateFromProps()
(2)、shouldComponentUpdate()
(3)、render()
(4)、getSnapshotBeforeUpdate()
(5)、componentDidUpdate()
3、卸载组件:由ReactDOM.UnmountComponent
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vx8inKFm-1630217723709)(D:\百度下载\尚硅谷前端学科全套教程(总126.90GB)\2.尚硅谷前端学科–高级技术\尚硅谷React全家桶教程\react全家桶资料\02_原理图\react生命周期(新)].png)
React 的调和算法(Diffing 算法)
1)、react/vue中的可以有什么作用?(key的内部原理是什么?)
2)、为什么遍历数组列表是,key最好不要使用index
1、虚拟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,随后渲染到页面
注意:
(1)、展开运算符可以使用到数组的拼接 let arr3 = […arr1,…arr2]
(2)、展开运算符可以用于函数的传参function sum(…a){}
(3)、展开运算符不能用与展开对象 console.log(…obj) 这里会报错
(4)、在ES6中展开运算符可以和花括号{},使用用来复制对象 var objclone = {…obj}
这里也可以修改,或增加属性, var objclone = {…obj,name:“张三”,addrss:‘地球’} 假如已有name属性,
就修改name的值,还没有addrss属性,就添加该属性到该对象里
(5)、在react中,因为有react和babel,是允许展开运算符直接展开对象的,但不能随便使用,必须是在标签中使用
npm i create-react-app
创建新的 React 应用
网路请求跨域问题解决:
1、配置代理解决跨域(该方法只能配置一个代理):
1)、方法:
在package.json文件中添加一个代理,
“proxy”:“http://localhost:5000”,注意这里配置到端口号就可以了,
这里端口写的5000是因为我服务器开启的端口是5000,我页面react开启的端口是3000
2)、原理:
这里的发生跨域是因为,服务器开启的端开号和react开启的端口号不是同一个端口号,
但3000的react以5000端口发生请求时,5000端口的服务器接收到,并返回数据,
但是3000的react发现接收到的数据是5000端口发送的,和自己的端口不同,
就拒绝接收该数据,就注造成了跨域的现象。
所以要解决这种跨域问题,可以引入一个第三者中间件,配置一个代理的服务器,该服务器是一个小型的服务器,
并且这个代理服务器开启的端口是和3000的react开启的端口是也一样的,配置发送请求的地址是开启5000端口的
服务器地址
这样一来,3000的react发生请求时,就会发送给3000的代理服务器,3000的代理服务器在发送给5000的服务器,
5000的服务将数据返回给3000的代理服务器,3000的服务器再将数据传递给3000的reac他,
3000的react发现传递数据的服务器开启的端口也是3000,就接收该数据
3)、注意:
因为这里react也开启了端口,当我们想服务器发送请求时,如果react中存在该数据文件,就直接获取该文件数据,
不会发送请求给代理服务器,如果发现react中不存在该数据文件,才会发送请求给代理服务器,访问后台服务器是否有该数据,
如果有则返回该数据,没有就没有啥
2、创建setupProxy.js文件配置多个代理:
1)、方法:
在src文档中创建一个setupProxy.js文件,在js文件里写代码:
第一步:导入http-proxy-middleware包
第二步:创建一个函数,并将这个函数暴露出去
这里proxy是一个函数,函数包两个参数,
第一个参数:’/api1’,当访问地址中出现该字符串是,表示访问该代理,通过该代理访问后端服务器,
第二个参数:是一个对象,包含:
target:通过该代理,要访问的目标后端服务器
changeOrigin:控制服务器收到的请求头中Host字段的值 如果为true则得到的请求头是 localhost:5000,
如果不设置或者为false这请求头就是 react的 localhost:3000,对于严格服务器检查到时可能会做出一些限制
pathRewrite:重写访问路径,该属性是为了将访问的地址中的 ‘/api1’ 字符串通过正则表达式替换成空字符串 ‘’
const proxy = require('http-proxy-middleware')
module.exports = function(app){
app.use(
proxy('/api1',{ //遇见 /api1 前缀的请求,就会触发代理配置
target:'http://localhost:5000', //请求转发给谁
changeOrigin:true, //控制服务器收到的请求头中Host字段的值 如果为true则得到的请求头是 localhost:5000,
//如果不设置或者为false这请求头就是 react的 localhost:3000,对于严格服务器检查到时会做出一些限制
pathRewrite:{'^/api1':''}
}),
proxy('/api2',{
target:'http://localhost:5001',
changeOrigin:true,
pathRewrite:{'^/api2':''}
})
)
}
//#region
/**
*
*
*
*
*
*
*
*/
//#endregion
3、注意:
以上两种方式只能选一种使用,不能同时使用
1、设计状态时要考虑全面,列如带有网络的组件,要考虑请求失败怎么办。
let obj = {a:{b:1}}
const {a} = obj //传统的解构赋值
const {a:{b}} = obj //连续解构赋值
const {a:{b:value}} = obj //连续解构赋值+重命名,这里将obj中a中的b的值赋值给value,
1)、ajax ,jquery中封装的 ajax,以及axios都属于 用xhr发送请求
这里介绍axios: https://github.com/axios/axios
引入axios库,
axios.method(访问地址,传递参数).then(成功函数,失败函数)
例如:
axios.get('https://www.baidu.com?XXX=XXX').then(response=>{
console.log("成功了",response)
},
error=>{
console.log("失败了",error)
}).catch(err=>{
console.log(err)
});
axios.post('https://www.baidu.com',{params:{id:1111,name:'zhangshan'}}).then(response=>{
console.log("成功了",response)
},
error=>{
console.log("失败了",error)
}).catch(err=>{
console.log(err)
});
2)、fetch 向服务器发送请求:(官方文档:https://github.github.io/fetch/)
fetch符合关注分离原则,也就是将一些大的操作拆分成小的操作,每一小的的操作都会返回一个数据给web端。
fetch(访问地址).then(成功函数,失败函数).then(成功函数并取值,失败函数输出错误信息)
//这里的第一then,没有返回最终数据,只返回一个是否和该网站连接成功过,如果成功可以调用成功函数,成功函数可以使用自身的json()函数得到一个promise对象,我们需要将这个promise对象返回出去,继续使用then获取具体数据,如果失败,就调用失败 函数,并返回一个初始化的promise对象中断promise链接。
列如:
1)、(使用fetch 未优化)
fetch('https://github.github.com/?q=zhag').then(
response=>{
console.log("链接成功");
return reponse.json(); //注意:response.json()好像只能使用一次,使用多了会提示错误
},
error=>{
console.log("链接失败");
return new Promise(()=>{})
}
).then(
response=>{
console.log("获取成功",response.items)
}
error=>{
console.log("获取失败",error);
}
)
2)、(使用fetch和async、await 优化代码)
a = async function(){
try{
const response = await fetch('https://github.github.com/?q=xxx')
const result = await response.json()
console.log(result)
}catch(err){
console.log(“出现错误:",err)
}
}
(1)、通过父组件实现通信,具体实现是通过父组件传递的prop实现通信,给发送消息的的子组件传递一个函数,子组件利用这个函数返回数据给父组件,父组件将数据接收到更新到状态中去,父组件在将状态中的数据通过prop传递给接收数据的子组件,从而达到了兄弟之间的通信的目的
(2)、使用PubBubJs库实现兄弟组件之间的通信,该库是通过发送消息和订阅消息的形式达到通信效果。
(需要下载pubsub-js库 npm i pubsub-js)(pubsub使用文档 https://github.com/mroderick/PubSubJS)
连个兄弟组件都要引入pubsub-jsku
import pubsub from “pubsub-js”
*1、订阅消息的组件:(一般在组件挂载完成开启订阅,卸载组件前关闭消息订阅)
var token = pubsub.subscribe(消息名,function(msg,data){…}) //token 表示获取当前订阅的编号,m当不需要订阅时,就可以拿这个token进行关闭,msg一般不会写,用下划线站位
pubsub.unsubsceibe(token)
*2、发送消息的组件:
pubsub.public(消息名,数据)
SPA (single page application):单一页面应用,整个应用只有一个完整的页面
点击链接不会跳转页面,页面不会更新,只是局部的更新
数据需要通过ajax请求获取,并在前端异步展现
单页面多组件
路由:
什么是路由?
一个路由是一个映射关系(key:value)
key为路径,value可能是function或者component
路由分类:
1、后端路由:
1)、理解:value是function,用来处理客户端提交的请求
2)、注册路由:router.get(path,function(request,response,next){});
3)、工作过程:当node接收到一个请求时,根据请求路径找到匹配的路由,调用路由中的函数来 处理请求,防护响应请求数据
2、前端路由:
1)、浏览器路由,value是component,用来展示页面内容。
2)、注册;路由
3)、工作过程:当浏览器的path变为/test时,当前路由就会变为Test组件
路由器管理路由
浏览器的历史记录是一个栈的形式
路由的基本使用:
1)、明确号界面中的导航区,展示区
2)、导航区的a标签改为Link标签
<Link to='/test'></Link>
3)、展示区写Route标签进行路径的匹配:
<Route path='test' component = {Test}/>
4)、在的最外侧包裹一个 或者
<BrowserRouter><App/></BrowserRouter>
1)、写法不同:
一般组件
路由组件
2)、存放位置不同:
一般组件:components
路由组件:pages
3)、接收到的props不同:
一般组件:写组件标签是传递了什么就可以接受什么
路由组件:接受到三固定的属性
history:
go: ƒ go(n)
goBack: ƒ goBack()
goForward: ƒ goForward()
push: ƒ push(path, state)
replace: ƒ replace(path, state)
location:
pathname: “/home”
search: “”
state: undefined
match:
params: {}
path: “/home”
url: “/home”
1)、NavLink可以实现路由连接的高度,通过activeClassName指定样式名
2)、标签内容是一个特殊的标签属性 children
3)、通过this.props.children 可以获取到标签体内容
{/*About
Home*/}
{/* 原生HTML中,靠跳转不同的页面 ,*/}
{/* 在react中靠路由连接实现切换组件 小写带杠不加点*/}
{/* NavLink 的设置理念是单我们点击到某个路由组件就给这个路由组件添加一个class属性,
默认情况下是添加一个active,如果要修改class名可以使用,activeClassName="demo" 进行修改 ,
注意如果我们引用了bootstrap.css 要给demo中的属性值增加权重*/}
{/* About
Home */}
{/* 标签体也是标签中的一个属性,属性名叫children ,props也会帮我们收集该属性并传递给子组件*/}
<MyNavLink to='/about'>About</MyNavLink>
1)、通常情况下,path和component是一一对应的关系
2)、Switch可以提高路由的匹配效率(单一匹配)
{/* 注册路由 */}
{/* 如果路由有多个时,要用Switch组件包裹一下,可以解决效率问题 */}
1)、public/index.html 中引入样式时,将 ./ 写成 / (常用)
2)、public/index.html 中引入样式时,将 ./ 写成 %PUBLIC_URL%/ (常用)
3)、将使用hash路由器 HashRouter
1)、默认使用的是模糊匹配(简单级:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
2)、开启严格匹配:
3)、严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
1)、一般写在所有路由注册的最下方,当所有路由都无法匹配时,就执行该条代码,跳转到Redirect指定的路由
2)、具体编码:
<Switch>
{/* 严格匹配和模糊匹配默认情况下是开启模糊匹配的,如果需要天剑严格匹配就需要添加 exact 属性 */}
<Route path='/about' component={About}></Route>
<Route path='/home' component={Home}></Route>
<Route path='/test' component={Test}></Route>
<Redirect to="/home"></Redirect>
{/* 当路径都没有匹配成功时,Redirect就从定向给页面指定一个组件进行展示 */}
</Switch>
嵌套路由,多级路由
1)、注册子路由时有写上父路由的path值
2)、路由的匹配是按照注册路由的顺序进行的
<Switch>
<Route path="/home/news" component={News}></Route>
<Route path="/home/message" component={Message}></Route>
<Redirect to='/home/news'></Redirect>
</Switch>
1)、使用params传递参数:
在路由连接中传递参数:
<Link to={`/home/message/${id}/${title}`}></Link>
这里传递了两个数据 id和title
在注册路由时,接收声明:
<Route path="home/message/:id/:title" component={XXXX}/>
在XXX.jsx中接受数据:
const {id,title} = this.props.match.params
2)、使用search传递参数:
在路由链接中传递参数:
<Link to=``{/home/message/?${id}&${title}}/>
在注册路由时,不需要接收声明,正常写注册即可
<Route path="home/message" component={XXX}/>
在XXX.jsx中接受数据:
需要使用querystring库
import qs from 'querystring';
const {id,title} = qs.parse(this.props.location.sreach.substring(1))
urlencoded编码格式: key=value&key=value的形式
querystring库中有两个函数qs.stringify(obj),可以将对象形式的数据转换成urlencoded编码格式的数据
qs.parse(urlencoded),可以将urlencoded编码格式的数据转换成对象格式的数据
3)、传递state参数:
在路由链接中的to中传递的是一个对象 :
<Link to={{pathname='/home/message',state:{id:item.id,title:item.title,content:item.content}}}>
在注册路由时,不需要接收声明,正常写注册即可
<Route path="home/message" component={XXX}/>
在xxx.jsx中接受数据:
const {id,title,content} = this.props.location.state || {}
借助this.props.history对象上的API操作对操作路由跳转、前进、houtui
—this.props.history.push(path,state)
—this.props.history.replace(path,state)
—this.props.history.goForward()
—this.props.history.goBack()
—this.props.history.go(nember)
withRouter函数是react-router-dom中的一个函数,该函数可以接收一个一般组件作为参数,并返回一个和路由组件有一样功能的组件
import React, { Component } from 'react'
import { withRouter } from 'react-router-dom'
class Header extends Component {
forward=()=>{
this.props.history.goForward()
}
back=()=>{
this.props.history.goBack()
}
go=()=>{
// go传递数字,整数表示前进几步,负数表示后退几步
this.history.go(2)
}
render() {
return (
<div>
<div className="page-header"><h2>React Router Demo</h2></div>
<button onClick={this.back}>回退</button>
<button onClick={this.forward}>前进</button>
<button onClick={this.go}>go</button>
</div>
)
}
}
export default withRouter(Header)
//withRouter的作用,可以接收一个一般组件,并将这个一般组件上挂载Router上特有的API
1)、底层原理不同:
BrowserRouter使用的是H5的history API,不兼容IE9一下的版本
HashRouter使用的是URL的哈希值
2)、url表现形式不一样
BrowserRouter的路径中没有#,列如:localhost:30000/demo/test
HashRouter的路径中有#,列如:localhost:3000/#/demo/test
3)、刷新后对路由state参数的影响
BrowserRouter没有任何影响,因为state保存在history对象中,
HashRouter刷新后会导致路由state参数的丢失
4)、备注:HashRouter可以用于解决一些路径错误相关的问题。
1)、 antd库用react封装了一些组件库,我们对于一些不需要太大要求的的页面,可以使用蚂蚁金服旗下的Ant Design也就是antd,
官方文档:https://ant.design/components/overview-cn/
(1)、 安装依赖: npm i antd craco-less
(2)、 在使用该组件的组件中,引入antd库获取要是用的组件 import {Button} from ‘antd’
(3)、 在在App.css中引入antd.css import “atnd/antd/dist/antd.css”
2)、因为我们在使用antd库时,不管我们使用了都少组件,整个程序都需要引入整个antd.css样式,导入浪费空间,所以需要按需导入样式
官方文档:https://ant.design/docs/react/use-with-create-react-app-cn#%E9%AB%98%E7%BA%A7%E9%85%8D%E7%BD%AE
这里要学习时最新版本的知识,一些低版本的东西可能被弃用了,(当前版本为4.x)
(1)、 安装依赖: npm i @craco/craco
(2)、 修改package.json中的配置
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
}
(3)、在项目的根目录下创建一个craco.config.js文件,用于修改默认配置的样式
(4)、如果还是App.css,就将其改成App.less,并将import "atnd/antd/dist/antd.css"改成import “atnd/antd/dist/antd.less”
(5)、在craco.config.js中配置,修改默认样式:
const CracoLessPlugin = require('craco-less');
module.exports = {
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
modifyVars: { '@primary-color': '#1DA57A','@link-color': 'red' },
javascriptEnabled: true,
},
},
},
},
],
};
(6)、其他的样式配置官网:https://ant.design/docs/react/customize-theme-cn
1、什么是redux?
1)、redux是一个专门做状态管理的js库(不是react插件库)。
2)、它可以在react、angular、vue等项目中,基本与react配合使用
3)、作用集中式管理react应用中多个组件共享的状态。
2、什么情况下使用redux
1)、某个组件的状态 需要让其他组件可以随时拿到(共享)
2)、一个组件需要改变另外一个组件的状态(通信)
3)、总体原则:能不用就不用,如果不用就不比较吃力才考虑使用。
3、redux中的三个核心:
1)、action:
动作对象
包含两个属性:
type:标识属性,值为字符串,唯一,必须属性
data:数据属性,值类型任意,可选属性
列子:{type:‘ADD_STUDENT’,data:{name:‘tom’,age:18}}
2)、reducer
初始化状态,加工状态
加工时,根据就的state和action,产生新的state的纯函数
3)、store
将state,action,reducer联系在一起的对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O6W9TWsP-1630217723711)(D:\百度下载\尚硅谷前端学科全套教程(总126.90GB)\2.尚硅谷前端学科–高级技术\尚硅谷React全家桶教程\react全家桶资料\02_原理图\redux原理图.png)]
(1)、去除Count组件的自身状态
(2)、src下建立:
-redux
-store.js
-count_reducer.js
-count_action.js
-constant.js
(3)、store.js:
1)、引入redux中的createStore函数,创建一个store
2)、createStore调用时要传入一个为其服务的reducer
3)、记得暴露store
// 该文件专门用于暴露一个store对象,真应用只有一个store对象
//引入createStore,专门由于创建redux中最为核心的store对象
import {createStore} from 'redux'
//引入为count组件的服务reducer
import countReducer from './count_reducer'
//暴露
export default createStore(countReducer)
(4)、count_reducer.js
1)、该文件是创建一个为Count组件服务的函数,该文件本质也是函数,函数包含两个参数preState和action
2)、函数根据action中的type类型进行加工:
3)、加工完成后返回一个新的state给store
/**
* 该文件适用于创建一个为count组件服务的reducer,reducer的本质就是一个函数
* reducer函数会接到两个参数,分别为:之前的状态{preState},动作对象{action}
*/
import { INCREMENT,DECREMENT } from "./constant";
export default function countReducer(PreState=0,action){
const {type,data} = action;
switch(type){
case INCREMENT: return PreState+data;
case DECREMENT :return PreState-data;
default:
return PreState;
}
}
(5)、count_action.js:
1)、该文件用于创建action对象
/**
* 该文件专门为Count组件生成action对象
*/
import {INCREMENT,DECREMENT} from './constant'
export const createIncrementAction=(data)=>({type:INCREMENT,data})
export const createDecrementAction=(data)=>({type:DECREMENT,data})
(6)、constant.js:
1)、该文件适用于管理常用变量,防止程序员写错代码
/**
* 该模块适用于定义action对象中type类型的常量值,
* 目的只有一个防止程序员写错,以及方便下修改
*/
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
(7)、在index.js中检测store中的状态的改变,一旦发生改变就需要重新渲染
备注:redux只负责管理状态,至于状态的改变驱动到页面的展示,要靠我们自己写,可以使用this.setState
/*
这里写在需要更新的组件中,这里触发更新需要引入store.js文件,使用store自带的subscribe接收状态的更新,通过挂载到组件挂载完成时进行实时监听状态的改变。
*/
import store from '../../redux/store'
componentDidMount(){
store.subscribe(()=>{
this.setState({})
})
}
(8)、 可以在index.js入口文件中:
//检测redux中的状态改变,如redux的状态发生改变,那么重新渲染App组件
store.subscribe(()=>{
reactDom.render(<App/>,document.getElementById('root'))
})
1)、同步action指,action是一个object对象
export const createIncrementAction =(data)=>({type:'increment',data})
2)、异步action指的是action的值是一个function
export const createIncremnetAsyncAction = (data,time)=>{
return ()=>{
setTimeOut(()=>{store.dispatch(createIncrementAction(value))},time)
}
}
3)、在使用异步action时需要引入一个thunk,
npm i redux-thunk
4)、在store.js文件引出thunk,和applyMiddleware
// 该文件专门用于暴露一个store对象,真应用只有一个store对象
//引入createStore,专门由于创建redux中最为核心的store对象
import {createStore,applyMiddleware} from 'redux'
//引入为count组件的服务reducer
import countReducer from './count_reducer'
//用于支持异步action
import thunk from 'redux-thunk'
//暴露
export default createStore(countReducer,applyMiddleware(thunk))
5)、备注异步action不是必须的,也可通过等待异步时间,在去发布action
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fhxcp6MM-1630217723713)(D:\百度下载\尚硅谷前端学科全套教程(总126.90GB)\2.尚硅谷前端学科–高级技术\尚硅谷React全家桶教程\react全家桶资料\02_原理图\react-redux模型图.png)]
react-redux是一Facebook公司旗下的:
react-redux模型:
1)、所有的UI组件都应该包裹一个容器组件,他们是父子关系。
2)、容器组件是真正和redux打交道的,里面可以随意使用redux的api
3)、UI组件中不能使用任何redux的api
4)、容器组件会传递UI组件:(1)、redux中所有保存的状态,(2)、用于操作状态的方法
5)、备注:容器给UI传递:状态,操作状态的方法,均通过props传递
(1)、明确两个概念:
1)、UI组件:不能使用任何redux的api,只负责展示页面
2)、容器组件:负责和redux通信,将结果交给UI组件
(2)、创建一个容器组件—————靠react-redux的connect函数
-src
-containers
-Count
-index.jsx
(3)、在容器组件中的index.jsx中
1)、引入子UI组件
import CountUI from '../../components/Count';
2)、下载react-redux: npm i react-redux
3)、 引入connect连接函数
import connect from 'react-redux'
4)、导入store,注意这里不使用 import导入,通过在App.js渲染容器组件时通过key:value的形式引入
//App.js
import React, { Component } from 'react'
import store from './redux/store'
import Count from './container/Count'
export default class App extends Component {
render() {
return (
<div>
{/* 给容器传递store */}
<Count store={store}/>
</div>
)
}
}
5)、在容器组件中书写代码:
//引入Count中的UI组件
import CountUI from '../../components/Count'
// import {createIncrementAction,createDecrementAction,createIncrementAsyncAction} from '../../redux/count_action'
import {createDecrementAction,createIncrementAction,createIncrementAsyncAction} from '../../redux/count_action'
//引入connect用于连接UI组件与redux
import {connect} from 'react-redux'
//mapStateToProps函数的返回值作为状态传递给UI组件,返回的key就是,state 是指redux中的store.getState
const mapStateToProps=(state)=>{
return {count:state}
}
//这里的dispatch是指store.dispatch
const mapDispatchToProps=(dispatch)=>{
return{
increment:(e)=>{dispatch(createIncrementAction(e*1))},
decrement:(e)=>{dispatch(createDecrementAction(e*1))},
incrementAsync:(e,time)=>{dispatch(createIncrementAsyncAction(e,time))}
}
}
//创建一个并暴露一个container的容器组件,connect包含连个参数,都必须是函数,每个函数中都有一个参数,第一个函数的参数是state,第二个函数是指dispatch函数
export default connect(mapStateToProps,mapDispatchToProps)(CountUI)
6)、这里的connect包含两个参数,第二个参数可以是一个函数,也可以是一个对象
对象的写法:{
jia:createIncrementAction,
jian:createDecrementAction
}
7)、使用connect就不用检测redux中的状态改变,如redux的状态发生改变,那么重新渲染App组件
也不需要重新写这行代码
store.subscribe(()=>{
reactDom.render(<App/>,document.getElementById('root'))
})
8)、在使用时需要传递state,在index.js文件中
import reactDom from "react-dom"
import store from "./redux/store"
import App from './App'
import {Provider} from 'react-redux'
reactDom.render(<Provider store={store}><App></App></Provider>,document.getElementById('root'))
//React-Redux 提供Provider组件,可以让容器组件拿到state。Provider在根组件外面包了一层,这样一来,App的所有子组件就默认都可以拿到state了。
1)、容器组件和UI组件整合一个文件
2)、无需自己给容器传递store,给包裹一个,即可
3)、使用react-redux后也不用自己代码来检测redux中状态的改变了,容器组件可以自己完成这个工作
4)、mapDispatch也可以简单的写成一个对象
5)、一个组件和redux打交道要经过那几步?
*1、定义号UI组件—不暴露
*2、引入connect成功一个容器组件,并暴露,
connect(
state=>({key:value}),
{key:value}
)(Count组件)
*3、在UI组件中通过this.props.xxxxx读取和操作状态
1)、重点:容器组件合并,使用redux中的combineReducers合并成一个总对象的状态
store.jsw文件
// 该文件专门用于暴露一个store对象,真应用只有一个store对象
//引入createStore,专门由于创建redux中最为核心的store对象
import {createStore,applyMiddleware,combineReducers} from 'redux'
//引入为count组件的服务reducer
import countReducer from './reducer/count'
import personReducer from './reducer/person'
//用于支持异步action
import thunk from 'redux-thunk'
//暴露
//汇总redux
const allReducer = combineReducers({he:countReducer,rens:personReducer})
export default createStore(allReducer,applyMiddleware(thunk))
2)、交个state的是总reducer,最后注意在组件中取出状态的时候,记得“取到位”。
1)、安装 npm i redux-devtools-extension
2)、store中进行配置
import {composeWithDevTools} from ‘redux-devtools-extension’
const store = create(allReducer,composeWithDevTools(applyMiddleware(thunk)))
(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函数中读取
//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
const Login = lazy(()=>import('@/pages/Login'))
//2.通过指定在加载得到路由打包文件前显示一个自定义loading界面
<Suspense fallback={<h1>loading.....</h1>}>
<Switch>
<Route path="/xxx" component={Xxxx}/>
<Redirect to="/login"/>
</Switch>
</Suspense>
(1). Hook是React 16.8.0版本增加的新特性/新语法
(2). 可以让你在函数组件中使用 state 以及其他的 React 特性
(1). State Hook: React.useState()
(2). Effect Hook: React.useEffect()
(3). Ref Hook: React.useRef()
(1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
(2). 语法: const [xxx, setXxx] = React.useState(initValue)
(3). useState()说明:
参数: 第一次初始化指定的值在内部作缓存
返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
(4). setXxx()2种写法:
setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
(1). Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
(2). React中的副作用操作:
发ajax请求数据获取
设置订阅 / 启动定时器
手动更改真实DOM
(3). 语法和说明:
useEffect(() => {
// 在此可以执行任何带副作用操作
return () => { // 在组件卸载前执行
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
}
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
(4). 可以把 useEffect Hook 看做如下三个函数的组合
componentDidMount()
componentDidUpdate()
componentWillUnmount()
(1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
(2). 语法: const refContainer = useRef()
(3). 作用:保存标签对象,功能与React.createRef()一样
<>>
可以不用必须有一个真实的DOM根标签了
一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信
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>
在应用开发中一般不用context, 一般都它的封装react插件
只要执行setState(),即使不改变状态数据, 组件也会重新render()
只当前组件重新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来优化
Vue中:
使用slot技术, 也就是通过组件标签体传入结构
React中:
使用children props: 通过组件标签体传入结构
使用render props: 通过组件标签属性传入结构, 一般用render函数属性
xxxx
{this.props.children}
问题: 如果B组件需要A组件内的数据, ==> 做不到
}>
A组件: {this.props.render(内部state数据)}
C组件: 读取A组件传入的数据显示 {this.props.data}
错误边界:用来捕获后代组件错误,渲染出备用页面
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
getDerivedStateFromError配合componentDidCatch
// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
console.log(error);
// 在render之前触发
// 返回新的state
return {
hasError: true,
};
}
componentDidCatch(error, info) {
// 统计页面的错误。发送请求发送到后台去
console.log(error, info);
}
props:
(1).children props
(2).render props
消息订阅-发布:
pubs-sub、event等等
集中式管理:
redux、dva等等
conText:
生产者-消费者模式
父子组件:props
兄弟组件(非嵌套组件):消息订阅-发布、集中式管理
={数据}>
子组件
后代组件读取数据:
//第一种方式:仅适用于类组件
static contextType = xxxContext // 声明接收context
this.context // 读取context中的value数据
//第二种方式: 函数组件与类组件都可以
{
value => ( // value就是context中的value数据
要显示的内容
)
}
### 注意
在应用开发中一般不用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来优化
## 7. render props
### 如何向组件内部动态传入带内容的结构(标签)?
Vue中:
使用slot技术, 也就是通过组件标签体传入结构
React中:
使用children props: 通过组件标签体传入结构
使用render props: 通过组件标签属性传入结构, 一般用render函数属性
### children props
xxxx
{this.props.children}
问题: 如果B组件需要A组件内的数据, ==> 做不到
### render props
}>
A组件: {this.props.render(内部state数据)}
C组件: 读取A组件传入的数据显示 {this.props.data}
## 8. 错误边界
#### 理解:
错误边界:用来捕获后代组件错误,渲染出备用页面
#### 特点:
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
##### 使用方式:
getDerivedStateFromError配合componentDidCatch
```js
// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
console.log(error);
// 在render之前触发
// 返回新的state
return {
hasError: true,
};
}
componentDidCatch(error, info) {
// 统计页面的错误。发送请求发送到后台去
console.log(error, info);
}
props:
(1).children props
(2).render props
消息订阅-发布:
pubs-sub、event等等
集中式管理:
redux、dva等等
conText:
生产者-消费者模式
父子组件:props
兄弟组件(非嵌套组件):消息订阅-发布、集中式管理
祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(用的少)