文章目录
- 前端学习--React部分
-
- 前言
- 1.React简介
- 2.React面向组件编程
-
- 2.1创建组件
- 2.2组件实例的三大属性
- 2.3事件处理
- [2.4 受控组件和非受控组件](#2.4 受控组件和非受控组件)
- 2.5高阶函数与函数柯里化
- 2.6生命周期
- [2.7虚拟DOM与DOM Diffing算法](#2.7虚拟DOM与DOM Diffing算法)
- 3.react脚手架
- [4.React ajax](#4.React ajax)
- 5.React路由
- [6.React UI组件库](#6.React UI组件库)
-
- [6.1流行的开源React UI组件库](#6.1流行的开源React UI组件库)
- 7.Redux
前端学习--React部分
前言
在学习React之前需要掌握的JavaScript基础知识有class(类)、ES6语法规范、npm包管理器、原型、原型链、数组的常用方法、模块化。如果上述知识点你有些淡忘,请前往我的博客主页进行查看。
在讲解过程中,老师会复习旧的知识点。请前往我的gitee链接中进行查看全部课程的代码:
https://gitee.com/marygood/tkreactstudy
下载文件去这里:
https://www.bootcdn.cn/
(下载的时候看好文件名)
1.React简介
官网给出的定义:用于构建用户界面的JavaScript库(只关注页面)。
通俗定义:将数据渲染为HTML视图的开源JavaScript库。
学习React的原因:
原生JS操作DOM繁琐且效率低,因为用DOM-API操作UI
JS直接操作DOM会使浏览器进行大量的重绘重排
原生JS没有组件化编码方案,代码复用率低
1.1React的特点
采用组件化模式,声明式编码,提高开发效率及组件复用率
在React Native中可以使用React语法进行移动端开发
使用虚拟DOM+优秀的Diffing算法,尽量减少与真实DOM的交互
注:最后一点很重要,它是React高效的原因。
问:为什么使用虚拟DOM?因为操作页面的代价比操作js大得多。
实现流程:把数据交给React,它便会拿着这些数据生成虚拟DOM,之后将虚拟DOM转换为真实DOM,最后将这些呈现在页面上。
补充:数据放在状态(state)中,不是随便放置的。
1.2引入文件
1.react.js (核心库):核心库要在react-dom之前引入
2.react-dom.js :提供操作DOM的react扩展库
3.babel.min.js:解析JSX语法代码转为JS代码的库,即ES6==>ES5;JSX==>JS
需要注意的是引入文件一定要按照这个顺序。
1.3JSX
🍉JSX简介与使用
全称 JavaScript XML ,是react定义的一种类似于XML的JS扩展语法,本质是
React.createElement(component, props, ...children)
方法的语法糖。JXS最终产生的虚拟DOM
就是一个JS对象。详细代码请前往码云链接。
🍉JSX语法规则
1.定义虚拟DOM时,不要写引号
2.标签中混入js表达式时用{}
3.样式的类名指定不要用class,要用className
4.内联样式,要用style={{key:value}}的形式去写
5.只有一个根标签
6.标签必须闭合
7.标签首字母
(1)小写默认为html标签:
<good></good>
//不是html标签--报错 (2)大写默认为组件:
<Good></Good>
//未定义组件--报错
1.4模块与组件
🍉模块
- 向外界提供特定功能的js程序。随着业务逻辑增加,代码越来越多且复杂,此时js一般会拆成多个js文件来编写,一般一个js文件就是一个模块
- 作用:复用js,简化js的编写,提高js的运行效率
- 模块化:当应用的js都以模块来编写的, 这个应用就是一个模块化的应用
🍉组件
- 用来实现局部功能效果的代码和资源的集合(html/css/js/image等等)。比如一个功能齐全的Header栏就是一个组件。
- 复用编码, 简化项目编码, 提高运行效率
- 组件化:当应用是以多组件的方式实现, 这个应用就是一个组件化的应用
1.5安装开发者工具
我自己直接在谷歌应用商店下载的,现在的发布者已经变为Meta。
打开
扩展程序
,找到安装好的React Developer Tool
,点击详情
,并勾选上以下内容。
2.React面向组件编程
2.1创建组件
🍉函数式组件
简单组件,没有state,本身没有三大属性,不过通过hooks也有了三大属性。
🍉类式组件
复杂组件,有state,有三大属性。
2.2组件实例的三大属性
🍉state属性
state
是组件对象最重要的属性, 值是对象(可以包含多个key-value的组合)- 组件被称为"状态机", 通过更新组件的
state
来更新对应的页面显示(重新渲染组件)代码实例:切换天气状态(简写版)
html<script type="text/babel"> // 实际开发中一定是写精简版本的 // 1.创建组件 class Weather extends React.Component{ // 初始化状态 state={isHot:false,wind:"微风"} render() { // 读取状态(解构赋值) const {isHot,wind} =this.state // 注意此处React事件绑定的写法 return <h1 onClick={this.changeWeather}>今天天气{isHot ? '热':'凉'},{wind}</h1> } // 和完整写法比这里用了箭头函数,就不再使用构造器 changeWeather=() => { // 获取原来的isHot值 const isHot=this.state.isHot // 注意:状态(不可以直接更改)必须通过setState进行更新,且更新是一种合并,不是替换。 // this.state.isHot=!isHot //错误写法 this.setState({isHot:!isHot}) } } // 2.渲染组件到页面 ReactDOM.render(<Weather/>,document.getElementById('test')) </script>
这里需要注意的是在render函数中创建虚拟DOM时,直接在标签中绑定事件,且事件写法不同于原生JS,如原生JS中的onclick事件,在react中要写成onClick,其他同理。其次,需要注意的是状态数据不能直接修改或更新,要使用setState。
🍉props属性
通过标签属性(创建虚拟DOM时直接添加的数据)从组件外向组件内传递变化的数据
传递props,即传递标签属性 -- props批量传递标签属性
组件标签的所有属性都保存在props中
props是只读的,不要在组件内部修改props
如果想对标签属性进行类型、必要性限制,需要引入prop-types库(不是必须的,看情况)
代码实例: 自定义用来显示人员信息
标准形式 -- 写在外部
html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>hello_react</title> </head> <body> <!-- 准备好一个容器 --> <div id="test"></div> <div id="test1"></div> <div id="test2"></div> <!-- 引入依赖 ,引入的时候,必须就按照这个步骤--> <!-- 引入react核心库 --> <script src="../js/react.development.js" type="text/javascript"></script> <!-- 引入react-dom,用于支持react操作DOM --> <script src="../js/react-dom.development.js" type="text/javascript"></script> <!-- 引入babel,用于将jsx转为js --> <script src="../js/babel.min.js" type="text/javascript"></script> <!--引入对于组件标签的限制--> <script src="../js/prop-types.js"></script> <!-- 此处一定要写babel,不写会默认为js,将报错 --> <script type="text/babel"> // 1.创建组件 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> ) } } // 对标签属性进行类型、必要性限制 Person.propTypes={ name:PropTypes.string.isRequired, //限制name必传,且为字符串 sex:PropTypes.string,//限制sex为字符串 age:PropTypes.number,//限制age为数值 speak:PropTypes.func,//现在speak为函数 } // 指定默认标签属性值 Person.defaultProps={ sex:'男', age:18 } // 2.渲染组件到页面 ReactDOM.render(<Person name="Bob" age={19} speak={speak}/>,document.getElementById("test")) ReactDOM.render(<Person name="Tom" sex="男"/>,document.getElementById("test1")) // 另一种 写法 const p={name:"Lily",age:30,sex:"女"} ReactDOM.render(<Person {...p}/>,document.getElementById('test2')) function speak() { console.log('我说话了') } </script> </body> </html>
简写形式 -- 写在内部
html<!-- 此处一定要写babel,不写会默认为js,将报错 --> <script type="text/babel"> // 1.创建组件 class Person extends React.Component{ // 对标签属性进行类型、必要性限制 static propTypes={ name:PropTypes.string.isRequired, //限制name必传,且为字符串 sex:PropTypes.string,//限制sex为字符串 age:PropTypes.number,//限制age为数值 } // 指定默认标签属性值 static defaultProps={ sex:'男', age:18 } render() { console.log(this) const {name,age,sex} = this.props return( <ul> <li>姓名:{name}</li> <li>性别:{sex}</li> <li>年龄:{age+1}</li> </ul> ) } } // 2.渲染组件到页面 ReactDOM.render(<Person name="Bob"/>,document.getElementById("test")) </script>
注意:
- 首字母小写的
propTypes
是类里的属性规则- 首字母大写的
PropTypes
是prop-types库里的内置对象函数组件使用props
html<!-- 此处一定要写babel,不写会默认为js,将报错 --> <script type="text/babel"> // 注意:到目前学习阶段,函数组件只能使用三大属性中props function Person(props){ const {name,age,sex} = props return( <ul> <li>姓名:{name}</li> <li>性别:{sex}</li> <li>年龄:{age}</li> </ul> ) } // 渲染组件到页面 ReactDOM.render(<Person name='Bob' sex='male' age='12'/>,document.getElementById('test')) </script>
🍉refs属性
组件内的标签可以定义
ref
属性来标识自己。this.refs
可以拿到真实DOM。1.字符形式的ref
html<!-- 此处一定要写babel,不写会默认为js,将报错 --> <script type="text/babel"> class Demo extends React.Component{ // 展示左侧输入框数据 showData = () =>{ const {input1}=this.refs alert(input1.value) // console.log(this) } // 展示右侧输入框的数据 showDataRight=()=>{ const {input2}=this.refs alert(input2.value) } render(){ return ( <div> <input ref="input1" type="text" placeholder="点击弹出提示数据" /> <button onClick = {this.showData}>点击</button> <input ref="input2" onBlur={this.showDataRight} type="text" placeholder="失去焦点弹出提示数据" /> </div> ) } } // 渲染组件到页面 ReactDOM.render(<Demo/>,document.getElementById('test')) /* ref与refs介绍: 1.只要有ref标识就会有refs属性(有ref就不用id了,功能类似) 2.这节使用的字符串形式的ref比较老旧,不推荐使用,因为效率低 */ </script>
2.回调函数形式的ref
回调函数:一种特殊的函数。它会作为参数传递给另一个函数,并在该函数被调用执行完毕后执行。
html<script type="text/babel"> class Demo extends React.Component{ // 展示左侧输入框数据 showData = () =>{ const {input1}=this alert(input1.value) } // 展示右侧输入框的数据 showDataRight=()=>{ const {input2}=this alert(input2.value) } // currentNode当前节点,两种写法,一种是完整版,一种是精简版 render(){ return ( <div> <input ref={(currentNode)=>{this.input1=currentNode}} type="text" placeholder="点击弹出提示数据" /> <button onClick = {this.showData}>点击</button> <input ref={ c => this.input2=c } onBlur={this.showDataRight} type="text" placeholder="失去焦点弹出提示数据" /> </div> ) } } // 渲染组件到页面 ReactDOM.render(<Demo/>,document.getElementById('test')) </script>
3.使用createRef(react最推荐的形式)
html<!-- 此处一定要写babel,不写会默认为js,将报错 --> <script type="text/babel"> class Demo extends React.Component{ // React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器的使用是需要一一对应的 myRef=React.createRef() // myRefRight=React.createRef() // 展示左侧输入框数据 showData = () =>{ alert(this.myRef.current.value) } // 展示右侧输入框的数据 showDataRight=()=>{ alert(this.myRefRight.current.value) } // currentNode当前节点,两种写法,一种是完整版,一种是精简版 render(){ return ( <div> <input ref={this.myRef} type="text" placeholder="点击弹出提示数据" /> <button onClick = {this.showData}>点击</button> <input ref={this.myRefRight=React.createRef()} onBlur={this.showDataRight} type="text" placeholder="失去焦点弹出提示数据" /> </div> ) } } // 渲染组件到页面 ReactDOM.render(<Demo/>,document.getElementById('test')) /* createRef(API)这种方式,是目前react最推荐的。 */
2.3事件处理
html<!-- 此处一定要写babel,不写会默认为js,将报错 --> <script type="text/babel"> class Demo extends React.Component{ /* 1.通过onXxx属性指定事件处理函数(注意大小写) a.React使用的是自定义(合成)事件,而不是使用原生DOM事件 -- 为更好的兼容性 b.React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)-- 为高效 2.通过event.target得到发生事件的DOM元素对象 -- 不要过度使用ref(有事件就不用ref) */ // 创建ref容器 myRef=React.createRef() // 展示左侧输入框数据 showData = () =>{ alert(this.myRef.current.value) } // 展示右侧输入框的数据 showDataRight=(event)=>{ alert(event.target.value) } // currentNode当前节点,两种写法,一种是完整版,一种是精简版 render(){ return ( <div> <input ref={this.myRef} type="text" placeholder="点击弹出提示数据" /> <button onClick = {this.showData}>点击</button> <input onBlur={this.showDataRight} type="text" placeholder="失去焦点弹出提示数据" /> </div> ) } } // 渲染组件到页面 ReactDOM.render(<Demo/>,document.getElementById('test')) </script>
2.4 受控组件和非受控组件
🍉非受控组件
html<!-- 此处一定要写babel,不写会默认为js,将报错 --> <script type="text/babel"> class Demo extends React.Component{ login = (event) =>{ event.preventDefault() //阻止表单提交 const {saveName,savePwd}=this alert(`姓名:${saveName.current.value},密码:${savePwd.current.value}`) } render(){ return ( <form action="http://www.baidu.com" onSubmit={this.login}> 用户名:<input ref={this.saveName=React.createRef()} type = "text" name ="username"/> 密码<input ref={this.savePwd=React.createRef()} type = "password" name ="password"/> <button>登录</button> </form> ) } } // 渲染组件到页面 ReactDOM.render(<Demo/>,document.getElementById('test')) /* 现用现取,就属于非受控组件 */ </script>
🍉受控组件
html<!-- 此处一定要写babel,不写会默认为js,将报错 --> <script type="text/babel"> class Demo extends React.Component{ saveName = (event) =>{ this.setState({username:event.target.value}) } savePwd=(event)=>{ this.setState({password:event.target.value}) } login = (event) =>{ event.preventDefault() //阻止表单提交 const {username,password} = this.state //注意:username和password是值 alert(`姓名:${username},密码:${password}`) } render(){ return ( <form action="http://www.baidu.com" onSubmit={this.login}> 用户名:<input onChange={this.saveName} type = "text" name ="username"/> 密码<input onChange={this.savePwd} type = "password" name ="password"/> <button>登录</button> </form> ) } } // 渲染组件到页面 ReactDOM.render(<Demo/>,document.getElementById('test')) /* 随着输入维护状态,就属于受控组件。(类似vue中的双向数据绑定) 通俗理解:通过ref就是非受控,通过change就是受控。 建议写受控组件 */ </script>
2.5高阶函数与函数柯里化
html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>hello_react</title> </head> <body> <!-- 准备好一个容器 --> <div id="test"></div> <!-- 引入依赖 ,引入的时候,必须就按照这个步骤--> <!-- 引入react核心库 --> <script src="../js/react.development.js" type="text/javascript"></script> <!-- 引入react-dom,用于支持react操作DOM --> <script src="../js/react-dom.development.js" type="text/javascript"></script> <!-- 引入babel,用于将jsx转为js --> <script src="../js/babel.min.js" type="text/javascript"></script> <!--引入对于组件标签的限制--> <script src="../js/prop-types.js"></script> <!-- 此处一定要写babel,不写会默认为js,将报错 --> <script type="text/babel"> class Demo extends React.Component{ // saveFormData就是高阶函数 saveFormData = (dataType) =>{ return(event)=>{ // 调用变量形式的对象属性名 // 可以理解为加上中括号的dataType就成为了一个属性 this.setState({[dataType]:event.target.value}) } } login = (event) =>{ event.preventDefault() //阻止表单提交 const {username,password} = this.state //注意:username和password是值 alert(`姓名:${username},密码:${password}`) } // 能保证onChange等于的是一个函数即可,无需过度在意加不加括号 render(){ return ( <form action="http://www.baidu.com" onSubmit={this.login}> 用户名:<input onChange={this.saveFormData('username')} type = "text" name ="username"/> 密码<input onChange={this.saveFormData('password')} type = "password" name ="password"/> <button>登录</button> </form> ) } } // 渲染组件到页面 ReactDOM.render(<Demo/>,document.getElementById('test')) /* 高阶函数:如果一个函数符合下面2个规范中的任何一个,那么该函数就是高阶函数。 1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数 2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数 常见的高阶函数有:Promise、setTimeout、arr.map()等等 函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。 */ </script> </body> </html>
不过也不是一定要用函数的柯里化实现,下面介绍不用函数柯里化实现的方法:
html<!-- 此处一定要写babel,不写会默认为js,将报错 --> <script type="text/babel"> class Demo extends React.Component{ // saveFormData就是高阶函数 saveFormData = (dataType,event) =>{ this.setState({[dataType]:event.target.value}) } login = (event) =>{ event.preventDefault() //阻止表单提交 const {username,password} = this.state //注意:username和password是值 alert(`姓名:${username},密码:${password}`) } // 能保证onChange等于的是一个函数即可,无需过度在意加不加括号 render(){ return ( <form action="http://www.baidu.com" onSubmit={this.login}> 用户名:<input onChange={event => this.saveFormData('username',event)} type = "text" name ="username"/> 密码<input onChange={event => this.saveFormData('password',event)} type = "password" name ="password"/> <button>登录</button> </form> ) } } // 渲染组件到页面 ReactDOM.render(<Demo/>,document.getElementById('test')) </script>
2.6生命周期
🍉引入
- React组件中包含一系列生命周期回调函数。我们在定义组件时,会在特定的生命周期回调函数中做特定的工作。
- 生命周期回调函数也可以称为:生命周期钩子函数 == 生命周期函数 ==生命周期钩子。
补充react写法:
- 行内样式写法:
style={``{opacity:opacity}}
。- 对象中属性名和属性值相同时触发简写:
{opacity:opacity}
简写为{opacity}
。🍉react生命周期(旧)
1.初始化阶段: 由ReactDOM.render()触发---初次渲染 1)constructor() 2)componentWillMount() 3)render(): 常用,初始化或者更新渲染 4)componentDidMount(): 常用,一般在这个钩子中做一些初始化的事,如开启定时器、发送网络请求、订阅消息 2.更新阶段: 由组件内部this.setSate()或父组件重新render触发 1)shouldComponentUpdate() -- 闸门,返回值是布尔值,为true时执行后续代码 2)componentWillUpdate() 3)render() 4)componentDidUpdate() 3.卸载组件: 由ReactDOM.unmountComponentAtNode()触发 1)componentWillUnmount() :常用,一般在这个钩子中做一些收尾的事,如关闭定时器、取消订阅消息
🍉react生命周期(新)
1.初始化阶段: 由ReactDOM.render()触发---初次渲染 1)constructor() 2)getDerivedStateFromProps 3)render(): 常用,初始化或者更新渲染 4)componentDidMount(): 常用,一般在这个钩子中做一些初始化的事,如开启定时器、发送网络请求、订阅消息 2.更新阶段: 由组件内部this.setSate()或父组件重新render触发 1)getDerivedStateFromProps 2)shouldComponentUpdate() 3)render() 4)getSnapshotBeforeUpdate -- 5)componentDidUpdate() 3.卸载组件: 由ReactDOM.unmountComponentAtNode()触发 1)componentWillUnmount() :常用,一般在这个钩子中做一些收尾的事,如关闭定时器、取消订阅消息 注:新出的也不常用
2.7虚拟DOM与DOM Diffing算法
经典面试题: 1)react/vue中的key有什么作用?(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,随后渲染到到页面 2.用index作为key可能会引发的问题: 1)若对数据进行:逆序添加、逆序删除等破坏顺序操作: 会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。 2)如果结构中还包含输入类的DOM(如input,虚拟DOM中标签属性少,input没有value属性): 会产生错误DOM更新 ==> 界面有问题。 3)注意! 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。 3.开发中如何选择key? 1)最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。 2)如果确定只是简单的展示数据,用index也是可以的。
3.react脚手架
3.1创建与启动
- 全局安装:
npm i -g create-react-app
- 切换到想要创建项目的目录,使用命令:
create-react-app hello-react
。(hello-react是文件名)- 用VSCode打开文件,并在终端(软件内)启动项目:
npm start
3.2react脚手架项目结构
🍉默认文件
public -- 静态资源文件夹 favicon.icon -- 网站页签图标 index.html -- 主页面 logo192.png -- logo图 logo512.png -- logo图 manifest.json -- 应用加壳的配置文件 robots.txt -- 爬虫协议文件 src -- 源码文件夹 App.css -- App组件的样式,App.js里引用 App.js -- App组件,子组件可以都写在components文件夹里,子组件文件夹首字母大写。路由组件写在pages文件夹 App.test.js -- 用于给App做测试 index.css -- 样式,通用样式,入口文件里引用 index.js -- 入口文件 logo.svg -- logo图 reportWebVitals.js -- 页面性能分析文件(需要web-vitals库的支持) setupTests.js -- 组件单元测试的文件(需要jest-dom库的支持)
🍉编码流程
拆分组件: 拆分界面,抽取组件 实现静态组件: 使用组件实现静态页面效果 实现动态组件: 3.1 动态显示初始化数据 3.1.1 数据类型 3.1.2 数据名称 3.1.3 保存在哪个组件? 3.2 交互(从绑定事件监听开始)
3.3TodoList案例
详细代码见前言中的gitee链接。
🍉todoList案例提要
- 写法的注意:
class -- className
、style="" -- style={``{}}
- 动态初始化列表,如何确定将数据放在哪个组件的
state
中?
1)某个组件使用:放在其自身的state
中
2)某些组件使用:放在他们共同的父组件state
中(官方称此操作为:状态提升)- 关于父子之间通信:
1)【父组件】给【子组件】传递数据:通过props传递
2)【子组件】给【父组件】传递数据:通过props传递,要求父提前给子传递一个函数- 注意
defaultChecked
和checked
的区别,类似的还有:defaultValue
和value
- 状态在哪里,操作状态的方法就在哪里
4.React ajax
4.1简介
- React本身只关注于界面, 并不包含发送ajax请求的代码
- 前端应用需要通过ajax请求与后台进行交互(json数据)
- react应用中需要集成第三方ajax库(或自己封装,但是基本不会自己封装)
4.2实现
- jQuery: 要操作DOM,并且比较重, 如果需要另外引入不建议使用
- axios: 轻量级, 建议使用
1)封装XmlHttpRequest对象的ajax
2)promise风格
3)可以用在浏览器端和node服务器端
4.3配置代理
方法一
在package.json中追加配置:
"proxy":"http://localhost:5000"
- 优点:配置简单,前端请求资源时可以不加任何前缀。
- 缺点:不能配置多个代理。
- 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)
方法二
创建代理配置文件:在src文件夹下创建setupProxy.js文件。
编写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': ''} }) ) }
说明:
优点:可以配置多个代理,可以灵活的控制请求是否走代理。
缺点:配置繁琐,前端请求资源时必须加前缀。
react 18版本写法:在后续的github案例中我自己用的这个。
//react 18版本写法 const {createProxyMiddleware} = require('http-proxy-middleware') module.exports = function (app) { app.use( createProxyMiddleware('/api1', { target: 'http://localhost:5000', changeOrigin: true, pathRewrite: {'^/api1': ''} }) ) }
4.4github搜索案例
详细代码在前言的gitee链接中。
🍉axios版本提要
连续解构赋值+重命名:
javascriptconst {keyWordElement:{value:keyWord}} = this
三元表达式:
javascriptisFirst ? <h2>欢迎使用,输入关键字,随后点击搜索</h2> : isLoading ? <h2>Loading......</h2> : err ? <h2 style={{color:'red'}}>{err}</h2>
如果你说什么都展现不出效果:
javascript//App.jsx文件 export default class App extends Component{ ... updateAppState = (stateObj) =>{ this.setState({...stateObj}) } ... }
🍉pubsub版本提要
工具库: PubSubJS 下载: npm install pubsub-js --save 使用: 1) import PubSub from 'pubsub-js' //引入 2) PubSub.subscribe('delete', function(data){ }); //订阅 3) PubSub.publish('delete', data) //发布 4) 要在组件的componentWilUnmount中取消订阅 注:谁用谁订阅,谁有订阅者所需的数据谁就发布。
🍉Fetch版本提要(了解)
发送ajax请求的方法: 原生AJAX:xhr jQuery:对xhr的封装 axios:对xhr的封装 fetch:不是库,是windows内置,且是Promise风格。 注:有些文章拉踩,不过还是要以主流为主。 fetch的特点:关注分离,兼容性不高,老版本浏览器不支持。
5.React路由
5.1相关理解
🍉对SPA应用的理解
- 单页Web应用(single page web application,SPA)。
- 整个应用只有一个完整的页面。
- 点击页面中的链接不会刷新页面,只会做页面的局部更新。
- 数据都需要通过ajax请求获取, 并在前端异步展现。
🍉路由的理解
什么是路由?
- 一个路由就是一个映射关系(key:value)
- key为路径, value可能是function或component
路由的分类
1.后端路由:
- 理解:value是function, 用来处理客户端提交的请求。
- 注册路由:
router.get(path, function(req, res))
- 工作过程:当node接受到一个请求时,根据请求路径找到匹配的路由,调用路由中的函数来处理请求,返回响应数据。
2.前端路由:
- 浏览器端路由,value是component,用于展示页面内容。
- 注册路由:
<Route path="/test" component={Test}>
- 工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件。
🍉react-router的理解
react的一个插件库。
- 安装:
npm i react-router-dom@5
。(注意是5版本)专门用来实现一个SPA应用。
基于react的项目基本都会用到此库。
5.2路由的基本使用
- 明确好界面中的导航区、展示区
- 导航区的a标签改为Link标签
<Link to="/xxxxx" >Demo</Link>
- 展示区写Route标签进行路径的匹配
<Route path='/xxxx 'component={Demo}/>
<App>
的最外侧包裹了一个<BrowserRouter>
或<HashRouter>
- 注意:详细代码见gitee。
5.3路由组件与一般组件
写法不同
- 一般组件:
<Demo/ >
- 路由组件:
<Route path="/demo" component={Demo}/>
存放文件位置不同:
- 一般组件:在components文件中
- 路由组件:在pages文件中
接收到的props不同:
一般组件:写组件标签时传递了什么,就能收到什么
路由组件:接收到三个固定的属性
history:
go: f go(n) goBack: f goBack() goForward: f goForward() push: f push(path,state) replace: f replace(path, state)
location:
pathname: "/about" search: "" state: undefined
match:
params: {} path: "/about" url: "/about"
5.4NavLink与封装NavLink
- NavLink可以实现路由链接的高亮,通过activeClassName指定样式名
- 标签体内容是一个特殊的标签属性
- 通过this.props.children可以获取标签体内容
- 注意:详细代码见gitee。
5.5Switch的使用
- 通常情况下,path和component是一一对应的关系。
- Switch可以提高路由匹配效率(单一匹配)。
- 详细代码见gitee。
5.6解决样式丢失问题
public/index.html 中引入样式时不写./写/(常用)
javascript<link rel="stylesheet" href="css/bootstrap.css">
public/index.html 中引入样式时不写﹒/写%PUBLIC_URL%(常用)
javascript<link rel="stylesheet" href="%PUBLIC_URL%/css/bootstrap.css">
使用HashRouter (不常用)
reactimport React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; import {BrowserRouter, HashRouter} from "react-router-dom"; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <HashRouter> <App/> </HashRouter> );
5.7路由的模糊匹配与严格匹配
- 默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
- 开启严格匹配:
<Route exact={true} path="/about" component={About}/>
- 严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
5.8Redirect的使用
一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由。
具体编码:
react<Switch> <Route path="/about" component={About}/> <Route path="/home" component={Home}/> <Redirect to="/about"/> </Switch>
5.9嵌套路由
- 注册子路由时要写上父路由的path值
- 路由的匹配是按照注册路由的顺序进行的
- 注意:详细代码见gitee
5.10向路由组件传递参数
🍉向路由组件传递params参数
路由链接(携带参数):
<Link to='/demo/test/tom/18'}>详情</Link>
注册路由(声明接收):
<Route path="/demo/test/:name/:age" component={Test}/>
接收参数:
const {id,title} = this.props.match.params
🍉向路由组件传递search参数
路由链接(携带参数):
<Link to=' /demo/test?name=tom&age=18'}>详情</Link>
注册路由(无需声明,正常注册即可):
<Route path="/demo/test" component={Test}/>
接收参数:
const isearch} = this.props.location
备注:获取到的search是urlencoded编码字符串,需要借助querystring解析
🍉向路由组件传递state参数
路由链接(携带参数):
react<Link to={{path:' /demo/test',state:{name : ' tom' ,age:18]}}>详情</Link>注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
接收参数:
this.props.location.state
备注:刷新也可以保留住参数
5.11push与repalce
- push:留下记录
- repalce:不留记录
- 注意:详细代码见gitee
5.12编程式路由导航
借助this.prosp.history对象上的API对操作路由跳转、前进、后退
this.prosp.history.push() this.prosp.history.replace() this.prosp.history.goBack() this.prosp.history.goForward() this.prosp.history.go()
注意:详细代码见gitee
5.13withRouter的使用
- withRouter可以加工一般组件,让一般组件具备路由组件所特有的API
- withRouter的返回值是一个新组件
reactimport React, {Component} from 'react'; import {withRouter} from "react-router-dom"; class Header extends Component { back = () => { this.props.history.goBack() } forward = () => { this.props.history.goForward() } go = () => { this.props.history.go(2) } render() { // console.log('Header组件收到的props是', this.props) return ( <div> <div className="page-header"> <h2>React Router Demo</h2> <button onClick={this.back}>回退</button> <button onClick={this.forward}>前进</button> <button onClick={this.go}>go</button> </div> </div> ); } } export default withRouter(Header);
5.14BrowserRouter与HashRouter
- 底层原理不一样:
(1) BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。
(2) HashRouter使用的是URL的哈希值- path表现形式不一样
(1) BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
(2) HashRouter的路径包含#,例如:localhost:3000/#/demo/test
- 刷新后对路由state参数的影响
(1) BrowserRouter没有任何影响,因为state保存在history对象中。
(2) HashRouter刷新后会导致路由state参数的丢失。- 备注: HashRouter可以用于解决一些路径错误相关的问题。
- 总的来讲BrowserRouter常用。
6.React UI组件库
6.1流行的开源React UI组件库
🍉material-ui(国外)
官网:
http://www.material-ui.com/#/
Github:
https://github.com/callemall/material-ui
🍉ant-design(国内蚂蚁金服)
官网:
https://ant.design/index-cn
Github:
https://github.com/ant-design/ant-design/
7.Redux
7.1Redux简介
- 什么是Redux?
- redux是一个专门用于做状态管理的JS库(不是react插件库)。
- 它可以用在react, angular, vue等项目中, 但基本与react配合使用。
- 作用: 集中式管理react应用中多个组件共享的状态。
- 什么情况下需要使用redux?
- 某个组件的状态,需要让其他组件可以随时拿到(共享)。
- 一个组件需要改变另一个组件的状态(通信)。
- 总体原则:能不用就不用, 如果不用比较吃力才考虑使用。
7.2Redux的工作流程
🍉redux原理图
🍉redux的三个核心概念
- action
- 动作的对象
- 包含2个属性
- type:标识属性, 值为字符串, 唯一, 必要属性
- data:数据属性, 值类型任意, 可选属性
- 例子:
{ type: 'ADD_STUDENT',data:{name: 'tom',age:18} }
- reducer
- 用于初始化状态、加工状态。
- 加工时,根据旧的state和action, 产生新的state的纯函数。
- store
- 将state、action、reducer联系在一起的对象
- 如何得到此对象?
- import {createStore} from 'redux'
- import reducer from './reducers'
- const store = createStore(reducer)
- 此对象的功能?
- getState(): 得到state
- dispatch(action): 分发action, 触发reducer调用, 产生新的state
- subscribe(listener): 注册监听, 当产生了新的state时, 自动调用
7.3求和案例
🍉纯react版
详细代码见gitee。
🍉redux精简版
- store.js:
- 引入redux中的createStore函数,创建一个store
- createstore调用时要传入一个为其服务的reducer
- 记得暴露store对象
- count_reducer.js:
- reducer的本质是一个函数,接收: preState,action,返回加工后的状态
- reducer有两个作用:初始化状态,加工状态
- reducer被第一次调用时,是store自动触发的,传递的preState是undefined
- 在index.js中检测store中状态的改变,一旦发生改变重新渲染
<App/>
- 备注: redux只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写。
- 注意:详细代码见gitee
🍉redux完整版
- 新增文件:
- count_action.js 专门用于创建action对象
- constant.js放置容易写错的type值
- 注意:详细代码见gitee
🍉异步action版
明确:延迟的动作不想交给组件自身,想交给action
何时需要异步action:想要对状态进行操作,但是具体的数据靠异步任务返回。
具体编码:
- yarn add redux-thunk,并配置在store中
- 创建action的函数不再返回一般对象,而是一个函数,该函数中写异步任务。
- 异步任务有结果后,分发一个同步的action去真正操作数据。
备注:异步action不是必须要写的,完全可以自己等待异步任务的结果了再去分发同步action
注意:详细代码见gitee
7.4初识react-redux
🍉对react-redux的理解
🍉连接容器组件与UI组件,react-redux基本使用
明确两个概念:
- UI组件:不能使用任何redux的api,只负责页面的呈现、交互等。
- 容器组件:负责和redux通信,将结果交给UT组件。
如何创建一个容器组件-----靠react-redux 的connect函数
- connect(mapStateToProps,mapDispatchToProps)(UI组件)
- mapstateToProps:映射状态,返回值是一个对象
- mapDispatchToProps:映射操作状态的方法,返回值是一个对象
备注1:容器组件中的store是靠props传进去的,而不是在容器组件中直接引入
备注2:mapDispatchToProps也可以是一个对象
注意:详细代码见gitee
7.5react-redux的优化
简写mapDispatch
Provider组件的使用
整合UI组件与容器组件
容器组件和UI组件组合成一个文件
无需自己给容器组件传递store,给
<App/>
包裹一个<Provider store={store}>
即可。使用了react-redux后也不用再自己检测redux中状态的改变了,容器组件可以自动完成这个工作。
mapDispatchToProps也可以简单的写成一个对象
一个组件要和redux"打交道"要经过哪几步?
定义好UI组件---不暴露
引入connect生成一个容器组件,并暴露,写法如下:
reactconnect( state =>({key:value}) {key : xxxxxAction} )(UI组件)
在UI组件中通过this.props.xxxxxxx读取和操作状态
注意:详细代码见gitee
7.6数据共享
定义一个Pserson组件,和Count组件通过redux共享数据
为Person组件编写: reducer、action,配置constant常量。
重点: Person的reducer和Count的Reducer要使用combineReducers进行合并,合并后的总状态是一个对象!!!
交给store的是总reducer,最后注意在组件中取出状态的时候,记得"取到位"。
注意:详细代码见gitee
7.7纯函数
- 一类特别的函数:只要是同样的输入(实参),必定得到同样的输出(返回)
- 必须遵守以下一些约束
- 不得改写参数数据
- 不会产生任何副作用,例如网络请求,输入和输出设备
- 不能调用Date.now)或者Math.random()等不纯的方法
- redux的reducer函数必须是一个纯函数
7.8redux开发者工具
首先在Google浏览器中下载扩展程序
Redux DevTools
安装:终端输入
npm i redux-devtools-extension
store中进行配置
reactimport {composewithDevTools} from 'redux-devtools-extension' const store =createStore(allReducer,composelithDevTools(applyMiddleware(thunk)))
7.9最终版
- 所有变量名字要规范,尽量触发对象的简写形式。
- reducers文件夹中,编写index.js专门用于汇总并暴露所有的reducer
- 注意:详细代码见gitee
7.10项目打包
- 打包:
npm run build
- 下载插件:
npm i serve -g
- 启动:
serve build
- 注意:在实际开发中,需要把build交给后端人员,再由后端人员配置到服务器中。