react是一个用于构建用户界面的js库
用户界面:HTML页面
React主要用来写HTML页面或者构建Web应用
如果从MVC的角度来看,React仅仅是视图层,也就是只负责视图的渲染,而并非提供了完整的M和C的功能。
React起源于Facebook的内部项目,后来又用来架设Instagram的网站,并于2013年5月开源
只需要描述UI(html)看起来是什么样,就跟写HTMl一样(JSX)
React负责渲染UI,并在数据变化时更新UI
const jsx=
hello react! 动态变化数据:{count}
组件是react最重要的内容
组件表示页面中的部分内容
组合、复用多个组件,可以实现完整的页面功能
…
vscode中在终端选项中新建终端
cd命令选择在当前文件夹中安装,命令:npm i react react-dom
来创建两个包
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J34I2z2P-1677566304596)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230215185607422.png)]
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>Documenttitle>
head>
<body>
<div id="root">div>
<script src="./node_modules/react/umd/react.development.js">script>
<script src="./node_modules/react-dom/umd/react-dom.development.js">script>
<script>
// 2、创建react元素
//参数一、元素名称
// 参数二、元素属性
// 参数三、元素子节点
const title = React.createElement('h1', null, 'hello React')
// 3、渲染react元素
// 参数一、要渲染的react元素
// 参数二、挂载点
ReactDOM.render(title, document.getElementById('root'))
script>
body>
html>
注意:ReactDOM中DOM全大写
总结:
<script src="./node_modules/react/umd/react.development.js">script>
<script src="./node_modules/react-dom/umd/react-dom.development.js">
之后导入react脚手架后会有更加简便的写法
const element = React.createElement(type,props, children1,[childrenN]);
type:元素类型 如:h1 div p等
可以是字符串如:div p h1
也可以是自定义组件:react原生组件等
props表示改元素上的属性,使用对象的方式表示
{
className:‘greeting’
}
children:表示元素内部的内容,可有多个children,可以是文字,也可以是另一个react元素
了解JSX后会有更加简便的写法,这里只需了解不需要记忆
ReactDOM.render(Reactele, DOMele)
Reactele:要渲染的react元素
DOMele:DOM对象,用于指定渲染到页面的DOM元素
进入正题
1、脚手架是开发现代Web应用的必备
2、充分利用Webpack\Babel\ESlint等工具辅助项目开发
3、零配置。无需手动配置繁琐的工具即可使用,方便更专注与项目
1、初始化项目,命令:npx create-react-app 项目名。
为加快初始化速度可以将npm的源进行更换,使用以下命令:
npm config set registry https://registry.npm.taobao.org
– 配置后可通过下面方式来验证是否成功
npm config get registry
– 显示出上述地址的话就是更换成功
可以在cmd中全局安装react脚手架,npm install -g create-react-app
之后便可直接用create-react-app 项目名 来构建react脚手架项目
2、使用cd命令转到刚刚创建的文件夹
3、启动项目,在项目根目录执行命令:npm start
4、测试,打开终端提供的网址出现启动页面则证明成功
初始化的文件夹中public文件夹中的index.html以及src中的index.js是首页也就是之前测试用的网页可以删除或者更改
1、导入react和react-dom两个包
import React from 'react'
import ReactDOM from 'react-dom'
2、调用React.createElement()方法创建react元素(以后会用JSX创建)
3、调用ReactDOM.render()方法渲染react元素到页面中
注意:在html页面中使用alt+b显示的页面不会显示调整的内容,也就是不能直接在浏览器中打开html页面,而是需要打开npm start命令打开的页面即 http://localhost:3000
JSX不是标准的ES语法,他是ES的语法扩展
需要用babel编译处理后,才能在浏览器中使用,而脚手架中已经给了babel环境
create-react-app脚手架中已经有默认的配置,无需手动配置
编译JSX语法的包是@babel/preset-react包
JSX是JavaScript XML的简写 表示在JavaScript代码中写入XML格式(HTML)的代码
优势:声明式语法更加直观,与HTML结构相同,降低了学习成本提升了开发效率
JSX是React的核心内容
1、使用JSX语法创建React元素
const title=Hello JSX
//创建react元素
react元素实则就是标签及其属性内容
2、使用ReactDOM.render()方法渲染react元素到页面中
ReactDOM.render(title,root)//渲染创建好的react元素
ReactDOM.render是 React 的最基本方法用于将模板转为 HTML 语言,并插入指定的 DOM 节点。一个节点只能插入一个title元素,后来插入的title元素会将其覆盖
react元素属性名使用驼峰命名
特殊属性名:
class->className
for->htmlFor
tabindex->tabIndex
const title=Hello JSX
const dv=(hello JSX)
const name='Jack'
const dv=(
你好,我叫:{name}
)
ReactDOM.render(dv,root)
注意:
{1},{‘a’},{1+4}等都是合法的
函数调用表达式也可以 {fn()}
JSX自身也是JS表达式
JS对象不能直接在{}中使用
语句不能在{}中出现比如for循环
const h1=我是h1
const dv=嵌入表达式{h1}
const loadData=()=>{
if(isLoading){
return loading...
}
return 数据加载完成
}
const title=(
条件渲染:{loadData()}
)
ReactDOM.render(title,document.getElementById('root'))
const title=(
条件渲染:{isloading?(loading...):(数据加载完成)}
)
ReactDOM.render(title,document.getElementById('root'))
逻辑与运算符&&
&&运算符若为true则会返回最后一个表达式值
const title=(
条件渲染:{isloading&&loading...}
)
ReactDOM.render(title,document.getElementById('root'))
const songs=[
{id:1,name:'痴心绝对'},
{id:2,name:'向我这样的'}
]
const list={
{songs.map(item=>- {item.name}
)}
}
1、行内样式–style
const title=
2、类名–className (推荐)
const title=
function Hello(){
return{我的第一个组件}
}
//const Hello=()=>我的第一个组件
ReactDOM.render( ,document.getElementById('root')
class Hello extends React.Component{//创建类组件
render(){
return Hello!
}
}
ReactDOM.render( ,root)//渲染组件
将组件单独放入一个js文件中
1、创建js文件
2、导入React
3、创建组件
4、导出该组件
5、在需要使用该组件的js文件中导入该组件
6、渲染组件
//hello.js
import React from 'react'
class Hello extends React.Component{
render(){
return Hello!
}
}
//导出Hello组件
export default Hello
//index.js
import Hello from './Hello'
//渲染导入的Hello组件
ReactDOM.render( ,root)
class App extends React.Component{//类组件
handleClick(){
console.log('单击事件')
}
render(){
return (
}
)
}
function App(){//函数组件
function handleClick(){
console.log('单击事件')
}
return (
)
}
function handleClick(e){
e.preventDefault()
console.log('事件对象',e)
}
class Hello extends React.Component{
//初始化
construct(){//构造函数
super()//es6中的要求用于继承父类构造函数的函数
//初始化state
this.state={
count:0
}
}
//初始化简化语法(推荐)
/*
state={
count:0
}
*/
render(){
return (
有状态的组件,{this.state.count}
)
}
}
class Hello extends React.Component{
state={
count:0
}
onIncrement(){
//this为undifined 此程序出错
this.setState({
count:this.state.count+1
})
}
render(){
return(
计数器:{this.state.count}
)
}
}
上面的代码发生了错误原因如下:
解决方法:
1、箭头函数绑定事件
利用箭头函数自身不绑定this的特点
class Hello extends React.Component{
state={
count:0
}
onIncrement(){
this.setState({
count:this.state.count+1
})
}
render(){//利用箭头函数更改调用时的this指向
return(
计数器:{this.state.count}
react
)
}
}
2、Function.prototyoe.bind()
利用ES5中的bind方法,将事件处理程序中的this与组件实例绑定到一起
class Hello extends React.Component{
constructor(){
super()
this.onIncrement=this.onIncrement.bind(this)//将this绑定到constructor的this
state={
count:0
}
}
onIncrement(){
this.setState({
count:this.state.count+1
})
}
render(){
return(
计数器:{this.state.count}
)
}
}
3、class的实例方法
class Hello extends React.Component{
state={
count:0
}
onIncrement=()=>{//利用箭头函数将this指向当前实例
this.setState({
count:this.state.count+1
})
}
render(){
return(
计数器:{this.state.count}
)
}
}
绑定步骤:
1、在state中添加一个状态,作为表单元素的value值(表单绑定state)
state值的改变会使value值改变
state={txt:''}
2、给表单元素绑定change事件,将表单元素的值设置为state的值(state绑定表单)
当表单中的value值通过输入改变时同时也会改变state中的值
this.setState({txt:e.target.value})}/>
优化:使用一个事件处理程序同时处理多个表单元素
步骤:
1、给表单元素添加name属性,名称与state相同(便于分辨和更改state同名的值)
这里名称为txt
name的一个作用是分辨表单标签,而另一个重要作用就是其命名与state中相应变量同名,以便于用标签name名与[]组合来锁定变量以更方便的改变变量的数据,也就是相当于对state中相应变量的一个索引
2、根据表单元素类型获取相应的值
3、在change事件处理程序中通过[name]来修改对应的state值
//根据表单元素类型获取值
const value=(target.type==='checkbox'?target.checked:target.value)
//根据name设置对应的state
this.setState({
[name]:value//当创建对象并将该对象的键包装在数组括号[]中时,可以动态改变其键名
})
import React from'react'
class FormHandle extends React.Component{
constructor(){
super()
this.state={
txt:'',
txtarea:''
}
}
handleForm=e=>{
//获取当前事件的Dom对象
const target=e.target;
//根据类型获取值
const value=target.type==="checkbox"
?target.checked:target.value
//获取name
const name=target.name
//设置state值
this.setState({
[name]:value//当创建对象并将该对象的键包装在数组括号[]中时,可以动态改变其键名
})
}
render(){
return (
)
}
}
export default FormHandle
借助于ref,使用原生DOM方式来获取表单元素值
ref:用于获取DOM或组件
步骤:
1、调用React.createRef()方法创建一个ref对象
constructor(){
super()
this.txtRef=React.createRef()
}
2、将创建好的ref对象添加到文本框中
3、通过ref对象获取到文本框的值
console.log(this.txtRef.current.value)
Pinlun.js
import React from 'react'
class Pinlun extends React.Component{
//初始化状态
state={
comments:[
{id:1,name:'jack',content:'沙发!!!'},
{id:2,name:'rose',content:'板凳'},
{id:3,name:'tom',content:'楼主好人'}
],
userName:'',
usercontent:''
}
handleForm=(e)=>{
const target=e.target;
const {name,value}=target;
this.setState({
[name]:value
})
}
renderList=()=>{
return this.state.comments.length===0
?(暂无评论)
:(
{this.state.comments.map(item=>(
-
评论者:{item.name}
{item.content}
))}
)
}
addComent=(e)=>{
const {comments,userName,userContent}=this.state
const newComments=[...comments,{
id:Math.random(),
name:userName,
content:userContent}]
this.setState({
comments:newComments
})
}
render(){
return (
{this.renderList()}
)
}
}
export default Pinlun
index.js
import React from'react'
import ReactDom from'react-dom'
import Pinlun from './component/Pinlun'
ReactDom.render( ,document.getElementById('root'));
index.html
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>Documenttitle>
head>
<body>
<div id="root">div>
body>
html>
组件是独立且封闭的单元,默认情况下,只能使用组件自己的数据,在组件化的过程中,我们将一个完整的功能拆分为多个组件,以更好的完成整个应用的功能。而在这个过程中,多个组件之间不可避免的要共享某些数据,为了实现这些功能,就需要打破组件的独立封闭性,让其与外界沟通,这个过程就是组件通讯
props:接受传递给组件的数据
步骤:
1、传递数据:给组件标签添加属性并赋值
2、接收数据:
函数组件通过参数props接收数据,
类组件通过this.props接收数据(类要额外加一个this)
function Hello(props){
console.log(props)
return (
接收数据:{props.name}
)
}
class Hello extends react.Component{
render(){
return {
接收的数据:{this.props.age}
}
}
}
字符串、数字、数组、函数、jsx等都可以
constructor(props){//将props传递给构造函数
super(props)
}
1、父组件-》子组件
2、子组件-》父组件
3、兄弟组件之间通讯
1、父组件提供要传递的state数据
2、给子组件标签添加属性,值为state中的数据
3、子组件中通过props接收父组件中传递的数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-li0UekvF-1677566304597)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230225173721916.png)]
思路:利用回调函数,父组件提供回调,子组件调用和,将要传递的数据作为回调函数的参数
1、父组件提供一个回调函数(用于接收数据)
2、将该函数作为属性的值,传递给子组件
3、子组件通过props调用回调函数
4、将子组件的数据作为参数传递给回调函数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LYYjJta3-1677566304598)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230225221423309.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P8DlHDAZ-1677566304599)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230225221407010.png)]
①需要将他们的共享状态提升到最近的公共父组件中,由公共父组件管理这个状态,即状态提升
②公共父组件职责:1、提供共享状态2、提供操作共享状态的方法
③要通讯的子组件只需要通过props接收状态或操作状态的方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xbmb2CSb-1677566304599)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230225175109607.png)]
总结就是子组件b传递数据给父组件,父组件再传递数据给子组件a
作用:跨组件传递数据
Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。
步骤:
1、调用React.createContext()来创建Provider(提供数据)和Consumer(消费数据)两个组件
const {Provider,Consumer}=React.createContext()
2、使用Provider组件作为父节点
3、设置value属性,表示要传递的数据,值可以是字符串、对象等,当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。
4、调用Comsumer组件接收数据
{data=>接收的数据是:{data}}
总结:父节点用Provider提供数据,要接收数据的子节点用Consumer接收
例子:
const {Provider,Consumer}=React.createContext()
class App extends React.Component{
render(){
return(
)
}
}
const Node=props=>{
return (
)
}
const SubNode=props=>{
return(
)
}
const Child=props=>{
return (
{
data=>我是子节点--{data}
}
)
}
function Hello(props){
return (
组件的子节点:{props.children}
)
}
ReactDOM.render(我是文本子节点 ,root)
渲染结果为:
组件的子节点:我是文本子节点
对于组件来说,props是外来的,无法保证组件使用者传入什么格式的数据
若传入数据格式不对,可能会导致组件内部错误
关键是使用者并不明确错误原因
因此需要有props校验
props校验:允许在创建组件的时候,就指定props的类型、格式等
作用:捕获使用组件时因为props导致的错误,给出明确的错误提示,增加组件的健壮性
步骤:
1、安装prop-types命令为:npm i props-types
2、导入prop-types包
import PropTypes from 'prop-types'
3、使用 组件名.propTypes={} 来给props添加校验规则
4、校验规则通过PropTypes对象来指定
import PropTypes from 'prop-types'
function App(props){
return(
hi,{props.colors}
)
}
App.propTypes={
//约定colors属性为array类型
//若类型不对则会报明确错误
colors:PropTypes.array
}
1、常见类型:array,bool,func,number,object,string,symbol等
2、React元素类型:element
3、必填项:isRequired
4、特定结构的对象:shape({ })
更多类型可以查看相关文档
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t9c1G7dN-1677566304600)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230226111853234.png)]
defaultProps={}
场景:分页组件->每页显示条数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MM1u1VmF-1677566304600)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230226115618139.png)]
当props的一个值没有被传入则会使用默认值
理解组件的生命周期有助于来理解组件的运行方式、完成更复杂的组件功能、分析组件错误原因等
组件的生命周期:组件从被创建到挂载到页面中运行,再到组件不用时卸载的过程
生命周期的每个阶段总是伴随着一些方法调用,这些方法就是生命周期的钩子函数
钩子函数的作用:为开发人员在不同阶段操作组件提供时机
只有类组件才有生命周期
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RQVqcB7A-1677566304601)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230226120513006.png)]
1、创建时(挂载阶段)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SLAXtdFB-1677566304601)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230226120715489.png)]
钩子函数 | 触发时机 | 作用 |
---|---|---|
constructor | 创建组件时,最先执行 | 1、初始化state 2、为事件处理程序绑定this |
render | 每次组件渲染时触发 | 渲染UI,不能在render调用setState() |
componentDidMount | 组件挂载(完成DOM渲染)后 | 1、发送网络请求 2、DOM操作 |
2、更新时
更新触发条件:接收新的 props,setState(),foceUpdate()
执行顺序:
render=》componentDidUpdate
钩子函数 | 触发时机 | 作用 |
---|---|---|
render | 每次组件渲染 | 渲染UI |
componentDidUpdate | 组件更新后 | 1、发送网络请求 2、DOM操作 注意:如果要setState必须要放在一个if中,因为setState会导致组件更新导致循环递归 |
componentDidUpdate(preProps){
//比较更新前后的props中的值是否相同来决定是否重新渲染组件,前一个props可以通过prevProps来获取
if(prevProps.count!==this.props.count){
this.setState({
})
}
}
3、卸载时
执行时机:组件从页面中消失
钩子函数 | 触发时机 | 作用 |
---|---|---|
componentWillUnmount | 组件卸载 | 执行清理工作,比如清理定时器等 |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MMI1DFud-1677566304602)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230226153624068.png)]
复用相似的功能(联想函数封装)
复用什么? 1、state2、操作state的方法
(即复用组件的状态逻辑)
两种方式:1、render props模式 2、高阶组件HOC
这两种方式不是新的API,而是利用React自身特点的编码技巧,演化而成的固定模式(写法)
我的理解:可以说是这个组件提供了一个接口让其它组件来共用state数据和方法
思路:将要复用的state和操作state的方法封装到一个组件中
如何拿到该组件中复用的state?
在使用组件时,添加一个值为函数的prop,通过函数参数来获取(需要组件内部实现)
{}}>
如何渲染任意UI?
使用该函数的返回值作为要渲染的UI内容(jsx)
{鼠标当前位置{mouse.x},{mouse.y}
}}/>
1、创建Mouse组件,在组件中提供复用的状态逻辑代码(1、状态,2、操作状态的方法)
2、将要复用的状态作为props.render(state)方法的参数,暴露到组件外部(把state的值通过参数传给组件的函数render,render函数名可以更改)
3、使用props.render()的返回值作为要渲染的内容
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UzHc7h0J-1677566304602)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230226160505338.png)]
总结:1、也就是由组件本身来提供state数据和操作state数据的方法,2、再用this.props.function(this.state)将state数据暴露给组件外部,3、然后由组件标签属性来提供JSX结构负责如何渲染这个数据。
简单来说就是再组件的render()函数中的return处调用组件的属性中的自定义的返回JSX的方法来渲染该自定义的jsx,
其实就是Mouze组件提供了一个接口render使得render{}中的组件/函数能够使用其state和state方法
Mouse.js
import React from 'react'
//创建Mouse组件
class Mouse extends React.Component{
//鼠标位置state
state={
x:0,
y:0
}
handleMouseMove=e=>{
this.setState({
x:e.clientX,
y:e.clientY
})
}
//监听鼠标移动事件
componentDidMount(){
window.addEventListener('mousemove',this.handleMouseMove)
}
render(){
return this.props.Mrender(this.state)
}
}
export default Mouse
index.js
import React from'react'
import ReactDOM from'react-dom'
import Mouse from './component/Mouse'
const jsx=(
{
return( 鼠标位置:{state.x},{state.y}
)
}}/>
)
ReactDOM.render(jsx,document.getElementById('root'));
总的来看如果不需要复用,按正常写则index.js中的jsx应该之间写在Mouse.js中的render函数中,让其渲染。但考虑到复用,render只将组件渲染需要用到的state数据通过函数的方式传给了组件,让组件在外部渲染时使用组件数据,且让组件属性Mrender的return来决定如何渲染,渲染怎样的jsx结构
并不是该模式叫做render props就必须使用名为render的prop,实际上,可以用任意名称的prop
仅仅是把prop是一个函数,并且告诉组件要渲染什么内容的技术叫做:render prop模式
{({x,y})=>鼠标的位置是{x},{y}
}
//组件内部
this.props.children(this.state)
其实就是把render函数写在了组件标签之间由children来传递参数
1、推荐给render props模式添加props校验
Mouse.propTypes={
render:propTypes.func.isRequired
}
2、应该在组件卸载时解除mousemove事件绑定
componentWillUnmount(){
window.removeEventListener('mousemove',this.handleMouseMove)
}
目的:实现状态逻辑复用
采用包装模式实现复用(函数包装组件)
高阶组件(HOC)是一个函数,接收要包装的组件,返回增强后的组件,也就是他的参数是一个组件而且返回值也是一个组件
在高阶组件内部创建一个类组件,在这个类组件中提供复用的状态逻辑代码,通过props将复用的状态传递给作为参数的组件
1、创建一个函数,名称约定以with开头
function withMouse(){}
2、指定函数参数,参数应该以大写字母开头(作为要渲染的组件)
function withMouse(WrappedComponent){}
3、在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回
function withMouse(WrappedComponent){
class Mouse extends React.Component{}
return Mouse
}
4、在该组件中,渲染参数组件,同时将状态通过props传递给参数组件
function withMouse(WrappedComponent){
class Mouse extends React.Component{//复用组件,用于负责复用的state数据和方法
state={
.......
}
//这里省略操作state的方法
render{
//不同的jsx结构渲染
return //这里可以通过扩展运算符...来把state的数据依次转为props属性
}
}
return Mouse
}
5、调用该高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中
//调用组件
const MousePosition=widthMouse(Position)
//渲染组件
所以高阶组件复用实质上是将组件中render函数中的jsx结构分离出来成为一个单独的组件,再利用函数的方式将这个组件作为参数与函数中的组件融合并返回一个新的组件实现复用,简单来说实质上就是两个组件的一次嵌套或者说融合
例子
Mouse.js
import React from 'react'
//创建高阶组件
function withMouse(WrappedComponent){
//创建提供复用的组件
class Mouse extends React.Component{
//鼠标位置state
state={
x:0,
y:0
}
handleMouseMove=e=>{
this.setState({
x:e.clientX,
y:e.clientY
})
}
//监听鼠标移动事件
componentDidMount(){
window.addEventListener('mousemove',this.handleMouseMove)
}
componentWillUnmount(){
window.removeEventListener('mousemove',this.handleMouseMove)
}
render(){
return //这里利用扩展运算符将state的数据依次转为props属性
}
}
return Mouse
}
export default withMouse
index.js
import React from'react'
import ReactDOM from'react-dom'
import withMouse from './component/Mouse'
const Position=props=>{//用于测试高阶组件
return (
鼠标当前位置:(x:{props.x},y:{props.y})
)
}
const MousePosition=withMouse(Position)//调用高阶组件(函数)
ReactDOM.render( ,document.getElementById('root'));//渲染
自我总结:通过对高阶组件和render props模式的学习可以发现,组件的复用关键点都是在render函数的renturn处也就是要渲染的jsx结构,它都是被分离了出来形成了单独的可以在外部自定义的组件或者函数,而复用的部分都是state或者操作state的部分
Mouse.displayName = `WithMouse${getDisplayName(WrappedComponent)}`
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component'
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NLzcQFd0-1677566304603)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230227094023715.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3ZCdd77i-1677566304603)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230227094519911.png)]
学习目标:
setState()更新数据是异步的
在setState更新之后立即用console输出可以发现数据是之前的
所以注意:使用该语法时后面的setState不能依赖于前面的setState
可以调用多次setState但只会在最后一次调用时render更新UI,这是为了性能
setState((state,props)=>{ })
参数
this.setState((state,props)=>{//与setState一样也是异步更新
return {
count:state.count+1
}
})
console.log(this.state.count)
this.setState(
(state,props)=>{},
()=>{console.log('这个回调函数会在状态更新完成后立即执行')}
)
JSX仅仅是createElement()方法的语法糖(简化语法)
JSX语法被@babel/preset-react插件编译为createElement()方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZH57YaD3-1677566304604)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230227102228380.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rAm3v2C0-1677566304604)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230227102309634.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dsPaFjE2-1677566304604)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230227103108005.png)]
解决方法:使用钩子函数 shouldComponentUpdate(nextProps,nextState)
作用:通过返回值决定该组件是否重新渲染,返回true表示重新渲染,false表示不重新渲染
触发时机:更新阶段的钩子函数,组件重新渲染前执行(shouldComponentUpdate->render)
souldComponentUpdate(nexntProps,nextState){
console.log("最新的state",nextState)
console.log("更新前的state",this.state)
}
纯组件:React.PureComponent 与 React.Component功能相似
区别:PureComponent内部自动实现了shouldComponentUpdate钩子,不需要手动比较
原理:纯组件内部通过比较前后两次的props和state的值来决定是否重新渲染组件
class Hello extends React.PureComponent{
render(){
return()
}
}
说明:纯组件内部的对比是shallow compare (浅层对比)
浅层对比对于值类型来说:比较两个值是否相同(直接赋值即可,没有坑)
浅层对比对于引用类型来说,只比较对象的引用地址是否相同(里面可能值不同,有坑)
虚拟DOM:本质上是一个JS对象,用来描述你希望在屏幕上看到的内容(UI)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eTz8agzh-1677566304605)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230227112441297.png)]
执行过程:
1、初次渲染时,react会根据初始state创建一个虚拟DOM对象(树)
2、根据虚拟DOM生成正真的DOM,渲染到页面中
3、当数据变化后,重新根据新数据,创建新的虚拟DOM对象
4、与上一次得到的DOM对象使用Diff算法对比,得到需要更新的内容
5、最终,React只将变化的内容更新到DOM中,重新渲染到页面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YJs2gD30-1677566304606)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230227112604567.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jhGtppkM-1677566304606)(C:\Users\ZYJ\AppData\Roaming\Typora\typora-user-images\image-20230227142733414.png)]
现在前端应用大多是SPA(单页应用程序),也就是只有一个HTML页面的应用程序,因为它的用户体验更好,对服务器的压力更小,所以更受欢迎。为了有效的使用单个页面来管理原来多页面的功能,前端路由应运而生
步骤:
安装npm install react-router-dom
导入路由的三个核心组件:Router/Route/Link
import {BrowserRouter as Router,Route,Link} from 'react-router-dom'
使用Router组件包裹整个应用
//...省略页面内容
//to属性:url的pathname
页面
const First=()=>(页面一的内容
)
页面一
Router组件:包裹整个应用,一个React应用只需要使用一次
两种常用Router:HashRouter和BrowserRouter
HashRouter:使用URL的哈希值实现(不推荐)
(推荐)BrowserRouter:使用H5的history API实现
Link组件:用于指定导航标签,最终会被编译成一个a标签,to属性表示浏览器地址栏中的pathname
Route组件:指定路由展示组件相关信息
//path属性:路由规则
//component属性:展示的组件
//Route组件写在哪,渲染出的组件就展示在哪
1、点击Link标签,修改了里卢兰其地址栏中的URL
2、React路由监听到地址栏URL的变化
3、React路由内部遍历所有Route组件,使用路由规则(path)与pathname进行匹配
4、当路由规则path能够匹配地址栏中的pathname时就展示该Route组件的内容(每一个route都会去匹配)
场景:点击登录按钮,登陆成功后,通过代码跳转到后台首页
编程式导航:通过JS代码来实现页面跳转
history是React路由提供的,用于获取浏览器历史记录的相关信息
push(path):跳转到某个页面,参数path表示要跳转的路径
class Login extends Component{
handleLogin=()=>{//编程式导航实现路由跳转
//...
this.props.history.push('/home')
}
render(){
//...
}
}
<Route path="/" component={Home}>Route>
问题:在link中用/login依然能够匹配到默认路由/
原因:默认情况下,React路由是模糊匹配模式
模糊匹配规则:只要link中的pathname(link的to属性/地址栏url的pathname)以route中path值开头,这两个就会匹配成功
比如:route中的path为/能匹配所有的pathname,/first能匹配/first,/first/a/b等
如何避免默认路由在任何情况下展示
给Route组件添加exact属性,让其变成精确匹配模式
精确匹配:只要path和pathname完全匹配时才会展示该路由
推荐:给默认路由添加exact属性
om’
+ 使用Router组件包裹整个应用
```react
//...省略页面内容
//to属性:url的pathname
页面
const First=()=>(页面一的内容
)
页面一
Router组件:包裹整个应用,一个React应用只需要使用一次
两种常用Router:HashRouter和BrowserRouter
HashRouter:使用URL的哈希值实现(不推荐)
(推荐)BrowserRouter:使用H5的history API实现
Link组件:用于指定导航标签,最终会被编译成一个a标签,to属性表示浏览器地址栏中的pathname
Route组件:指定路由展示组件相关信息
//path属性:路由规则
//component属性:展示的组件
//Route组件写在哪,渲染出的组件就展示在哪
1、点击Link标签,修改了里卢兰其地址栏中的URL
2、React路由监听到地址栏URL的变化
3、React路由内部遍历所有Route组件,使用路由规则(path)与pathname进行匹配
4、当路由规则path能够匹配地址栏中的pathname时就展示该Route组件的内容(每一个route都会去匹配)
场景:点击登录按钮,登陆成功后,通过代码跳转到后台首页
编程式导航:通过JS代码来实现页面跳转
history是React路由提供的,用于获取浏览器历史记录的相关信息
push(path):跳转到某个页面,参数path表示要跳转的路径
class Login extends Component{
handleLogin=()=>{//编程式导航实现路由跳转
//...
this.props.history.push('/home')
}
render(){
//...
}
}
<Route path="/" component={Home}>Route>
问题:在link中用/login依然能够匹配到默认路由/
原因:默认情况下,React路由是模糊匹配模式
模糊匹配规则:只要link中的pathname(link的to属性/地址栏url的pathname)以route中path值开头,这两个就会匹配成功
比如:route中的path为/能匹配所有的pathname,/first能匹配/first,/first/a/b等
如何避免默认路由在任何情况下展示
给Route组件添加exact属性,让其变成精确匹配模式
精确匹配:只要path和pathname完全匹配时才会展示该路由
推荐:给默认路由添加exact属性