一、React介绍
React这个项目是facebook开源出来的一个优秀前端库。
官网将React称为库(jquery库),React默认提供的只是前端的页面更新方案。并没有提供完整的前端所有核心内容。比如路由、状态机。这些都需要我们自己搭建。
在2013年5月开源出来。
提出了很多概念在当时都是比较前卫的思想。
组件化开发、虚拟dom加载。
React开发有两个分支:
- React-DOM:这是一个包。这个分支解决我们React开发WEB业务
- React-Native:专门用于开发移动端。只能开发android和ios
优势:
- 组件化开发、虚拟dom设计,对应项目来说性能有保证。
- 技术成熟、社区配置完善。适用于中大型web项目
- facebook团队维护,版本更新、担心后续维护
- 目前来说React在公司里面,适用于中大型项目。大公司用的比较多
二、搭建项目
目前React官方基于webpack封装了脚手架。
你们可以基于脚手架工具。快速搭建一个项目。
(1)创建项目
js
npx create-react-app 项目名字
脚手架工具的文档:
这种方式来创建项目,本地无需安装脚手架。就可以直接创建项目
临时在本地安装一个脚手架工具,创建项目,创建完成后,删除脚手架
在本地安装好脚手架,直接用本地脚手架搭建项目。这种方式我们不推荐。
js
npm i create-react-app -g
create-react-app 项目名字
创建项目,项目名字不能出现大写字母,不能中文
(2)启动项目
安装yarn包管理器。默认提示你采用yarn启动项目
js
yarn start
npm来启动项目
js
npm run start
(3)改造项目
src目录下面只留
- index.js:这个文件项目启动文件。类似于Vue中main.js以后需要全局挂载内容都可以在里面写
- App.js:后缀是js,其中这个是React组件。里面代码不是原生js代码。会在index默认加载App.js文件
剩下的文件夹需要大家自己创建
component:存放组件
utils:存放开发自己封装工具
assets:静态资源目录
libs:存放第三方包 sdk
apis:存放异步请求
三、组件的介绍
(1)概念介绍
组件化思想:针对页面UI部分,进行了页面的拆分。将公共的页面模板拆分,最后在自己组合在一起。
模块化思想:针对JS脚本设计,将代码进行模块拆分。减少当前脚本体积,提高复用性。比如很多的工具提取出来。
html
<html>
<head>
<script src="./a.js"></script>
<script src="./b.js"></script>
</head>
</html>
a.js
js
var m = 10
b.js
js
var m = 20
最早解决思路
采用IIFE来解决问题。
a.js
js
(function(){
var m = 10
})()
b.js
js
(function(){
var m = 20
})()
requirejs seajs等等。第三方开发的模块中代码。
ES6这个版本,官方也推出了模块化思想
js
import xxx from "./"
export {}
export default {}
工程化思想:以前我们项目在没有脚手架搭建的时候,都是自己下载包。自己来进行代码压缩混淆。
基于webpack或者vite这种打包工具。你们直接依赖这些工具完成前端项目构建,打包、部署
(2)组件创建
React中组件有两种
- 类组件开发:采用的面向对象开发思想。
- 函数组件开发:基于函数来设计组件。所有业务都是基于函数来开展
在目前项目开发过程中,两种组件都会接触到。
在React中我们组件后缀有两种格式,js
在里面写jsx代码,jsx
后缀支持的一种格式
安装插件
创建组件的时候
rcc
:创建一个类组件的模板
rfc
:创建一个函数组件的的模块
类组件的代码
js
import React, { Component } from 'react'
/**
* 在React中当写一个类,继承Component,当前这个类是一个组件
* Component:React提供的组件
*/
class Header extends Component {
/**
* render渲染函数,我们当前这个组件要显示模板,在render里面
* JSX代码
* @returns
*/
render() {
return (
<div>Header</div>
)
}
}
export default Header
React设计思想:将JS+HTML模板统一放在一个文件中,写完了代码后。JS 和HTML分开解析。最后html+css+js
原生JS代码
js
const array = [1,2,3]
let temp = ""
for(var i=0;i<array.length;i++){
temp += `<li>${array[i]}</li>`
}
obody.innerHTML = temp
函数组件的代码
js
import React from 'react'
function Content() {
return (
<div>Content</div>
)
}
export default Content
我们先学习类组件,后面再学习函数组件
(3)JSX概念
组件本身设计的一种模块,可以将代码抽取出来。再React中就是采用JSX来作为组件的语法
JSX = JavaScript + XML
JavaScript就是基于ES5\ES6引入过来规范。
XML:标签语言,表示组件中所有标签模板。代码解析的时候。XML标签就是转化HTML标记了。
接下来你再组件中写的所有代码都是JSX代码。浏览器无法直接解析。
在启动项目的时候
sql
yarn start
webpack就会开始打包项目。babel这个插件,将jsx代码转化为浏览器能是被的js代码。最后才浏览器运行
四、组件的页面
(1)组件中类名
js
<div className="">
每个组件都要求,必须提供根标签。否则报错
js
<React.Fragment></React.Fragment>
使用这个来作为根标签,最终页面不会渲染出来。解决报错问题
js
<></>
也是代表虚拟标签,最终并不会解析出来。
(2)组件中引入图片
jsx
import React, { Component } from 'react'
import logo from "../assets/images/logo-250px.645f24b5.png"
const logo2 = require("../assets/images/logo-250px.645f24b5.png")
/**
* 在React中当写一个类,继承Component,当前这个类是一个组件
* Component:React提供的组件
*/
class Header extends Component {
/**
* render渲染函数,我们当前这个组件要显示模板,在render里面
* JSX代码
* render函数:方法重写,调用render优先执行子类render
* @returns
*/
render() {
return (
<React.Fragment>
<div className='container'>
<div className='logo'>
<img src={logo2} alt="" />
</div>
<div>
<span>欢迎【xx】登录</span>
</div>
</div>
<p></p>
</React.Fragment>
)
}
}
export default Header
总结:
- 服务器网络图片,地址直接写死,动态加载都可以。
- 图片本地图片,采用import 或者 require的方式引入进来,动态设置src属性
在JSX中动态绑定一个属性,我们需要src={logo}
(3)样式设计
在JSX中支持两种样式设计
- 内联样式:直接在标签上面style属性。
- 外部样式:将样式文件提取到外部css文件中,引入进来使用
内联样式:JSX中的样式已经经过封装过了。
js
<span style={{color:"red",fontSize:"20px"}}>欢迎【xx】登录</span>
因为style对象可以接受多个css样式,所以我们提供的是一个对象,定义很多样式
外部样式:
在styles文件夹下面创建css文件header.css
js
.container{
display: flex;
justify-content: space-between;
height: 80px;
width: 100%;
box-shadow: 0px 0px 10px rgba(0,0,0,.3);
padding: 0px 15px;
box-sizing: border-box;
}
.container .logo{
width: 180px;
height: 60px;
border: 1px solid red;
margin-top: 10px;
}
.container .logo img{
width: 100%;
}
JSX代码
jsx
render() {
return (
<React.Fragment>
<div className='container'>
<div className='logo'>
<img src={logo2} alt="" />
</div>
<div>
<span>欢迎【xx】登录</span>
</div>
</div>
<p></p>
</React.Fragment>
)
}
(4)样式模块化
在组件中样式导入过后默认是全局的样式,
不管在哪个组件中,一旦名字一致,相互影响
为了解决这个问题,React提出了一个概念。模块化样式。
开发步骤 styles文件夹下面创建文件名字 tabA.module.css
js
.op{
color: pink;
}
.box span{
color:blue
}
组件中使用
js
import styles from "../assets/tabA.module.css"
<div clasName={styles.box}></div>
(5) 组件的内部数据
Vue中组件内部数据使用data来定义。只要放在data中,这个数据响应式变化。一旦发生数据更新,页面也会更新
在React中组件内部数据采用state来定义
语法一:在构造器中定义state变量
js
constructor(){
//只要有继承,构造器第一句话必须写super
//super代表调用父类构造器,先创建父类
//当你不写构造器的时候,编辑器默认添加constructor
super()
//state对象里面存放的数据,类似于Vuedata定义的数据
this.state = {
msg:"小王"
}
}
页面上使用这个变量的时候
jsx
<div>{this.state.msg}</div>
为了更加优化代码,一般会在render函数里面解构,在去使用
jsx
render() {
const {msg} = this.state
return (
<React.Fragment>
<div className='menu'>
<ul>
<li className='active'>拼团管理</li>
<li>拼团数据</li>
<li>扩展内容</li>
</ul>
</div>
<div className='main'>
<p>{msg}</p>
<TabA></TabA>
<TabB></TabB>
</div>
</React.Fragment>
)
}
语法二:无需再构造器中定义
jsx
import React, { Component } from 'react'
import "../assets/styles/content.css"
import TabA from './TabA'
import TabB from './TabB'
import TabC from './TabC'
export default class Content extends Component {
state = {
msg:"xiaofeifei"
}
render() {
const {msg} = this.state
return (
<React.Fragment>
<div className='menu'>
<ul>
<li className='active'>拼团管理</li>
<li>拼团数据</li>
<li>扩展内容</li>
</ul>
</div>
<div className='main'>
<p>{msg}</p>
<TabA></TabA>
<TabB></TabB>
</div>
</React.Fragment>
)
}
}
(6)组件事件绑定
基本语法
React标签上面事件都被封装了一次。JSX的规则
命名规范采用驼峰命名方式
Vue
vue
<div @click="check">
</div>
js
check(){
}
<div onClick={this.check}>
<div onChange={this.check}>
事件参数传递
js
<li onClick={this.check} className='active'>拼团管理</li>
绑定事件,提供事件函数,如果这个函数没有加括号。将函数地址放在这个模板中。
点击li元素,找到这个函数,并执行
js
<li onClick={this.check(123)} className='active'>拼团管理</li>
对于JSX来说,立即调用check这个函数。
解决这个问题方案:
js
<li onClick={()=>this.check("TabA")} className='active'>拼团管理</li>
给onClick这个事件绑定一个箭头函数,再这个箭头函数里面调用check函数。
只有事件被触发,箭头函数调用,check才会执行。传递参数过去
事件绑定中this指向问题
如果你函数是普通函数,默认拿到this结果为undefined
页面中绑定事件,节点来触发事件。React底层处理过,并不会直接返回节点。
js
import React, { Component } from 'react'
import styles from "../assets/styles/tabA.module.css"
export default class TabA extends Component {
state = {
count: 10
}
changeCount() {
//React底层处理过这个事件
console.log(this); //TabA
this.setState({
count:20
})
}
render() {
const { count } = this.state
return (
<div>
<p>state的数据:{count}</p>
<button onClick={this.changeCount.bind(this)}>点击触发</button>
</div>
)
}
}
如果函数是普通函数,绑定事件bind来改变this的指向,函数里面this指向当前组件。
js
import React, { Component } from 'react'
import styles from "../assets/styles/tabA.module.css"
export default class TabA extends Component {
state = {
count: 10
}
changeCount = ()=>{
console.log(this); //TabA
}
render() {
const { count } = this.state
return (
<div>
<p>state的数据:{count}</p>
<button onClick={this.changeCount}>点击触发</button>
</div>
)
}
}
总结:以后再React开发过程中,能用箭头函数就直接用箭头函数。
事件对象
只要有事件绑定,那一定会有event事件对象产生。
默认绑定不传递参数的时候,事件函数第一个参数就是event对象
js
changeCount = (event)=>{
console.log(event.target);
}
<button onClick = {this.changeCount}>
如果你的事件函数要传递参数
js
changeCount = (event,val)=>{
console.log(event,val);
}
<button onClick={(event)=>this.changeCount(event,123)}>点击触发</button>
遇到要传递参数的时候,外层箭头函数才是事件绑定的函数。传递给内部的函数使用
(7)组件数据更新
组件内部数据state来定义。直接修改state的值,页面并不会检测更新
js
check(val){
this.setState({
active:val
})
}
当React检测到调用这个函数,将原来值进行替换。render函数重新调用一次
当我们进行数据修改的时候,setState里面传递的新的对象,修改的值和会原来state的值进行合并。不会覆盖
更新规则
setState在更新数据的时候。合并操作。并不是覆盖操作
js
state = {
count: 10,
msg:"xiaowang"
}
changeCount = (event,val)=>{
this.setState({
count:val
})
}
修改的时候,指定了修改的变量。state原来的值没有变化,并不会被覆盖。
异步更新
setState这个函数,可以更新state的数据,并且调用render实现页面刷新。
但是本身是一个异步任务。无法立即获取修改的结果
js
changeCount = (event,val)=>{
//异步代码。
this.setState({
count:val
})
console.log(this.state.count); //原来的数据
}
为了性能考虑,设计的方案,不管是Vue还是React,我们都是异步更新
为了能获取更新过后的结果,我们提供回调函数
js
changeCount = (event, val) => {
//异步代码。
this.setState({
count: val
}, () => {
console.log(this.state.count); //得到更新过后结果
})
}
合并更新
setState一旦调用,数据发生更新,render就会被执行
js
changeCount = (event, val) => {
//异步代码。
this.setState({
count: val
})
this.setState({
count: 1000
})
}
一次性执行多个二setState,每个更新任务,都会放在队列中。
队列中任务会取出来进行合并,当发现多个任务的时候。后进入队列中将内容和前面任务合并了。
合并过后,只需要更新页面一次。不要频繁的触发
五、组件通信
父子组件通信
- 父传子:在父组件中定义值,通过动态参数传递给子组件,子组件接受结果
- 子传父:子组件调用父组件传递过来函数,将内容传递回去
父传子
组件的数据来源于两部分:
- 组件内部自身状态,state来定义
- 组件外部的状态,props接受
父组件
js
state = {
currentPage:1,
pageSize:3
}
const {currentPage,pageSize} = this.state
<Pageing currentPage={currentPage} pageSize={pageSize}>
子组件
如果是类组件,默认在this对象身上会有一个props的对象,这个对象默认代表外部数据。如果你传递。空对象
js
import React, { Component } from 'react'
import style from "../../assets/styles/fenye.module.css"
export default class Pageing extends Component {
//mounted 挂载完毕
componentDidMount(){
console.log(this.props); -->{currentPage:1,pageSize:3}
}
render() {
return (
<div className={style.fenye}>
</div>
)
}
}
将props的值取出来,页面渲染。父组件中变量产生数据变化,子组件跟着变化
子组件如果要针对props的数据进行处理,一般是在子组件计算属性。
不要出现将props的值赋值给state,然后再去用state
验证
父组件调用子组件的时候,如果遇到子组件针对参数有验证规则。我们严格按照子组件要求来传递参数
子组件可以添加验证规则,一旦验证规则生效。使用这个组件,可以根据规则来传递参数
数据类型校验
在React老版本中,需要我们自己下载验证包,但是在新版本里面无需下载,直接使用
js
import React, { Component } from 'react'
import style from "../../assets/styles/fenye.module.css"
import PropTypes from "prop-types"
class Pageing extends Component {
//mounted 挂载完毕
get totalPage() {
const { pageSize, total } = this.props
return Math.ceil(total / pageSize)
}
render() {
const { currentPage, pageSize, total } = this.props
return (
<div className={style.fenye}>
....
</div>
)
}
}
//在暴露给外部调用之前,我们内部先进行props的验证
Pageing.propTypes = {
currentPage:PropTypes.number.isRequired,
pageSize:PropTypes.number.isRequired
}
export default Pageing
指定接受的参数要进行验证。
也可以设置默认值
js
import React, { Component } from 'react'
import style from "../../assets/styles/fenye.module.css"
import PropTypes from "prop-types"
class Pageing extends Component {
//mounted 挂载完毕
get totalPage() {
const { pageSize, total } = this.props
return Math.ceil(total / pageSize)
}
render() {
const { currentPage, pageSize, total } = this.props
return (
<div className={style.fenye}>
....
</div>
)
}
}
Pageing.defaultProps = {
currentPage:3
}
export default Pageing
如果你没有传递对应属性,采用默认props值。父组件传递了属性。以父组件的数据为主
子传父
父组件传递一个函数给子组件,子组件调用这个函数。就可以将参数传递过去
父组件
js
getCurrentPage = (page)=>{
}
<Pageing getCurrentPage={this.getCurrentPage}>
子组件调用
js
fenye = (event)=>{
const page = event.target.getAttribute("index")
console.log(typeof page) //string
this.props.getCurrentPage(Number(page))
}
<span index={1} onClick = {this.fenye}>1</span>
六、组件封装插槽
在React中子组件需要接受父组件定义模板。可以用插槽来接受数据
js
import React, { Component } from 'react'
import style from "../../assets/styles/model.module.css"
export default class Model extends Component {
render() {
return (
<div className={style.container}>
<div className={style.modelContent}>
<h3>添加优惠券</h3>
{this.props.children}
{/* 存放表单 */}
<button>关闭</button>
</div>
</div>
)
}
}
this.props.children
代表接受父组件传递过来模板