目录
五、react脚手架
1、前端脚手架
* 对于现在比较流行的三大框架都有属于自己的脚手架
- vue的脚手架:vue-cli
- angular的脚手架:angular-cli
- react的脚手架:create-react-app
* 它们的作用都是帮助我们生成一个通用的目录结构,并且已经将我们所需的工程环境配置好。
- 目前这些脚手架都是使用node编写的,并且都是基于webpack的
- 所以我们必须在自己的电脑上安装node环境
* 安装node
- 官网地址:https://nodejs.org/en/download/
- 注意:这里推荐大家下载LTS(long-term support)版本,是长期支持版本,会比较稳定
- 安装过程中,会自动配置环境变量
- 安装时,会同时帮助我们安装npm管理工具
* 包管理工具
- 全称node package manager,即“node包管理器”
- 另外,还有一个大名鼎鼎的node包管理工具yarn。yarn是为了弥补npm的一些缺陷而出现的。早期的npm
存在很多的缺陷,比如安装依赖速度很慢、版本依赖混乱等等一系列问题。虽然从npm5版本开始,进行了很多
的升级和改进,但是依然很多人喜欢使用yarn
- react脚手架默认也是使用yarn:npm install -g yarn
2、yarn和npm命令对比
npm | yarn |
---|---|
npm install | yarn install |
npm install [package] | yarn add [package] |
npm install --save [package] | yarn add [package] |
npm install --save-dev [package] | yarn add [package] [--dev/-D] |
npm rebuild | yarn install --force |
npm uninstall [package] | yarn remove [package] |
npm uninstall --save [package] | yarn remove [package] |
npm uninstall --save-dev [package] | yarn remove [package] |
npm uninstall --save-optional [package] | yarn remove [package] |
npm cache clean | yarn cache clean |
rm -rf node_modules && npm install | yarn upgrade |
3、安装脚手架
* cnpm安装(了解)
- npm install -g cnpm --registry=https://registry.npm.taobao.org
* 安装react脚手架
- npm install -g create-react-app
* 创建react项目(注意:项目名称不能包含大写字母)
- create-react-app 项目名称
- npm run start
* 项目结构
- public/manifest.json:web app配置相关
- src/serviceWorker.js:注册pwa相关的代码(注意:需要被调用)
- public/robots.txt:爬虫协议
- src/reportWebVitals.js:谷歌定义的web性能指标
~ LCP:最大内容渲染时间
~ FID:首次输入延迟
~ CLS:累计布局偏移
~ FCP:首次内容绘制
~ TTFB:首字节到达的时间点
* 了解pwa
- 全称progressive web app,即渐进式web应用
- 添加上app manifest和service worker来实现pwa的安装和离线等功能
- 可以将一些移动端页面添加至桌面
- 允许一系列类似于native app相关的功能,比如实现了消息推送
* webpack是什么
- 静态模块打包器(依赖关系图)
* react脚手架中的webpack
- react脚手架将webpack相关的配置隐藏起来了
- 如果我们希望看到webpack的配置信息,应该怎么来做呢
~ 执行package.json中的一个脚本:{"scripts": {"eject": "react-scripts eject"}}
4、react脚手架项目基础代码
- src/index.js
// 使用jsx语法时会使用到(jsx的本质)
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App/>);
- src/App.js
import React from "react";
class App extends React.Component {
constructor() {
super();
this.state = {
counter: 0
}
}
render() {
return (<div>
<div>{this.state.counter}</div>
<button onClick={this.increment.bind(this)}>+1</button>
<button onClick={this.decrement.bind(this)}>-1</button>
</div>)
}
increment() {
this.setState({
counter: this.state.counter + 1
})
}
decrement() {
this.setState({
counter: this.state.counter - 1
})
}
}
export default App;
六、react组件化开发
1、组件化开发
* 分而治之的思想
* 什么是组件化开发呢?
- 我们将一个完整的页面分成很多个组件,每个组件都用于实现页面的一个功能块,而每一个组件又可以细分,
而组件本身又可以在多个地方复用
* react的组件化
- 组件化提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用
- 任何的应用都会被抽象成一棵组件树
* react的组件相对于vue更加的灵活和多样,按照不同的方式可以分成很多类组件
- 根据组件的定义方式,可以分为:函数组件(function component)和类组件(class component)
- 根据组件内部是否有状态需要维护,可以分成:无状态组件(stateless component)和有状态组件(stateful component)
- 根据组件的不同职责,可以分成:展示型组件(presentational component)和容器型组件(container component)
* 这些概念有很多重叠,但是他们最主要是关注数据逻辑和ui展示的分离
- 函数组件、无状态组件、展示型组件主要关注ui的展示
- 类组件、有状态组件、容器型组件主要关注数据逻辑
2、类组件
import React, {Component} from "react";
/**
* 一、类组件的定义有如下要求
* - 组件的名称是大写字符开头(无论类组件还是函数组件)
* - 类组件需要继承自React.Component
* - 类组件必须实现render函数
* - 标签名称不能大写(原生html不区分大小写,而jsx区分大小写)
*/
class App extends Component {
constructor() {
super();
this.state = {
counter: 0
}
}
render() {
return (<div>
<div>{this.state.counter}</div>
<button onClick={this.increment.bind(this)}>+1</button>
<button onClick={this.decrement.bind(this)}>-1</button>
</div>)
}
increment() {
this.setState({
counter: this.state.counter + 1
})
}
decrement() {
this.setState({
counter: this.state.counter - 1
})
}
}
export default App;
3、函数式组件
import React from "react";
/**
* 一、函数式组件的特点
* - 没有this对象
* - 没有内部的状态(hooks)
*/
export default function App() {
// 2、render函数的返回值
// 2.1、react元素
return (<div>黄婷婷</div>)
// 2.2、数组或fragments
// return ([<div>姜贞羽</div>, <div>孟美岐</div>])
// 2.3、portals
// 2.4、字符串或数字类型(渲染成文本节点)
// return "张婧仪"
// 2.5、布尔类型或null(什么都不渲染)
// return true
}
4、认识生命周期
* 生命周期是一个抽象的概念,在生命周期的整个过程,分成了很多个阶段
- 比如装载阶段(mount),组件第一次在dom树中被渲染的过程
- 比如更新过程(update),组件状态发生变化,重新更新渲染的过程
- 比如卸载过程(unmount),组件从dom树中被移除的过程
* react内部为了告诉我们当前处于哪些阶段,会对我们组件内部实现的某些函数进行回调,这些函数就是生命周期函数
- 比如实现componentDidMount函数:组件已经挂载到dom上时,就会回调
- 比如实现componentDidUpdate函数:组件已经发生了更新时,就会回调
- 比如实现componentWillUnmount函数:组件即将被移除时,就会回调
- 我们可以在这些回调函数中编写自己的逻辑代码,来完成自己的需求功能
* 我们谈react生命周期时,主要谈的类的生命周期,因为函数式组件是没有生命周期函数的
(后面我们可以通过hooks来模拟一些生命周期的回调)
5、常用生命周期
import React from "react";
class Cpn extends React.Component {
render() {
return (<h3>黄婷婷</h3>)
}
/**
* 1、componentWillUnmount()会在组件卸载及销毁之前直接调用
* - 在此方法中执行必要的清理操作
* - 例如:清除timer,取消网络请求或清除在componentDidMount()中创建的订阅等
*/
componentWillUnmount() {
console.log("卸载")
}
}
export default class App extends React.Component {
/**
* 1、如果不初始化state或不进行方法绑定,则不需要为React组件实现构造函数
* 2、constructor中通常只做两件事情
* - 通过给this.state赋值对象来初始化内部的state
* - 为事件绑定实例(this)
*/
constructor() {
super();
console.log("【挂载】之前会执行")
this.state = {
counter: 0,
show: true
}
}
render() {
console.log("【挂载】或【更新】之前会执行")
return (<div>
<h3>当前计数{this.state.counter}</h3>
<button onClick={e => this.increment()}>计数+1</button>
<hr/>
<button onClick={e => this.changeCpnShow()}>显示隐藏</button>
{this.state.show && <Cpn/>}
</div>)
}
/**
* 1、componentDidMount()会在组件挂载后(插入dom树中)立即调用
* 2、componentDidMount中通常进行哪里操作呢
* - 依赖于dom的操作可以在这里进行
* - 在此处发送网络请求就最好的地方(官方建议)
* - 可以在此处添加一些订阅(会在componentWillUnmount取消订阅)
*/
componentDidMount() {
console.log("挂载")
}
/**
* 1、componentDidUpdate()会在更新后被立即调用,首次渲染不会执行此方法
* - 当组件更新后,可以在此处对dom进行操作
* - 如果你对更新前后的props进行了比较,也可以选择在此处进行网络请求
* (例如:当props未发生变化时,则不会执行网络请求)
*/
componentDidUpdate(prevProps, prevState, snapshot) {
console.log("更新")
}
increment() {
this.setState({
counter: this.state.counter + 1
})
}
changeCpnShow() {
this.setState({
show: !this.state.show
})
}
}
6、不常用生命周期函数
* 除了上面介绍的生命周期之外,还有一些不常用的生命周期函数
- getDerivedStateFromProps:state的值在任何时候都依赖于props时使用;
该方法返回一个对象来更新state
- getSnapshotBeforeUpdate:在React更新dom之前回调的一个函数,可以获取
dom更新前的一些信息(比如说滚动位置);
- shouldComponentUpdate:该生命周期函数很常用,但是我们等待讲性能优化时
再来详细讲解
* 另外,React中还提供了一些过期的生命周期函数,这些函数已经不推荐使用
* 更详细的生命周期相关的内容,可以参考官网:
https://zh-hans.reactjs.org/docs/react-component.html
7、组件的嵌套和组件间的通信
- 概念
* 父组件在展示子组件,可能会传递一些数据给子组件
- 父组件通过【属性=值】的形式来传递给子组件数据
- 子组件通过【props】参数获取父组件传递过来的数据
* 父组件传递子组件-类组件和函数组件
- 类组件父传子通信
import React from "react";
class Header extends React.Component {
/*
// 默认的构造函数(非显式调用构造函数),new对象时传入的参数,全部由super()接收
constructor(props) {
// super()不传入props,源码中实例也会通过属性赋值的方式添加上props
super(props);
// 父类中有被调用
// this.props = props
}*/
render() {
return (<h3>{this.props.name}</h3>)
}
}
class Bodyer extends React.Component {
render() {
return (<div>
<Lefter></Lefter>
<Righter></Righter>
</div>)
}
}
class Lefter extends React.Component {
render() {
return (<h3>左边</h3>)
}
}
class Righter extends React.Component {
render() {
return (<h3>右边</h3>)
}
}
class Footer extends React.Component {
render() {
return (<h3>尾部</h3>)
}
}
export default class App extends React.Component {
render() {
return (<div>
<Header name="黄婷婷"></Header>
<Bodyer></Bodyer>
<Footer></Footer>
</div>)
}
}
- 函数组件父传子通信,以及属性验证
import React from "react";
import PropTypes from "prop-types";
function Header(props) {
return (<div>
<h3>姓名:{props.name}</h3>
<h3>年龄:{props.age}</h3>
<h3>朋友:</h3>
<ul>{props.friends.map(item => <li>{item}</li>)}</ul>
</div>)
}
/*
// 类组件写法
class Header extends React.Component {
static propTypes = {}
static defaultProps = {}
}*/
Header.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
friends: PropTypes.array
}
Header.defaultProps = {
age: 18
}
export default class App extends React.Component {
render() {
return (<div>
<Header name="孟美岐" friends={['黄婷婷', '佟丽娅']}/>
</div>)
}
}
- 子组件传递父组件:函数传递
import React from "react";
export class CounterButton extends React.Component {
render() {
const {onClick} = this.props
return (<div>
<button onClick={onClick}>+</button>
</div>)
}
}
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
render() {
return (<div>
<h2>当前计数:{this.state.counter}</h2>
<CounterButton onClick={e => this.increment()}/>
</div>)
}
increment() {
this.setState({
counter: this.state.counter + 1
})
}
}
8、组件通信综合案例
- src/layout/App.js
import React from "react";
import TabControl from "../components/TabControl";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
currentTitle: '新款'
}
// 通常不需要更改的数据,不在state中定义
this.titles = ['新款', '精选', '流行']
}
render() {
return (<div>
<TabControl itemClick={index => this.itemClick(index)} titles={this.titles}/>
<div>{this.state.currentTitle}</div>
</div>)
}
itemClick(index) {
this.setState({
currentTitle: this.titles[index]
})
}
}
- src/components/TabControl.js
import React from "react";
import PropTypes from "prop-types";
import "../styles/index.css"
export default class TabControl extends React.Component {
constructor(props) {
super(props);
this.state = {
currentIndex: 0
}
}
static propTypes = {
titles: PropTypes.array.isRequired
}
render() {
return (<div className="tab-control">
{this.props.titles.map((item, index) => {
return (<div className={"tab-control__item" + (index === this.state.currentIndex ? ' active' : '')}
key={item} onClick={e => this.itemClick(index)}>{item}</div>)
})}
</div>)
}
itemClick(index) {
this.setState({
currentIndex: index
})
this.props.itemClick(index)
}
}
- src/styles/index.css
.tab-control {
display: flex;
text-align: center;
line-height: 44px;
}
.tab-control__item {
flex: 1;
}
.active {
color: red;
}
9、React实现slot
- src/layout/App.js
import React from "react";
import "../styles/index.css"
export default class App extends React.Component {
render() {
/**
* <div class="layout"><h3>这是一段中文</h3></div>
* - div:元素
* - class:属性
* - h3:内容
*/
return (<div>
<NavBar className="navbar">
<div className="navbar__left"><</div>
<div className="navbar__center">标题</div>
<div className="navbar__right">...</div>
</NavBar>
<NavBar2 className="navbar"
navbarLeft={<div className="navbar__left"><</div>}
navbarCenter={<div className="navbar__center">标题</div>}
navbarRight={<div className="navbar__right">...</div>}/>
</div>)
}
}
class NavBar extends React.Component {
render() {
// 内容方式传入插槽
return (<div className={this.props.className}>
{this.props.children[0]}
{this.props.children[1]}
{this.props.children[2]}
</div>)
}
}
class NavBar2 extends React.Component {
render() {
// 属性方式传入插槽
return (<div className={this.props.className}>
{this.props.navbarLeft}
{this.props.navbarCenter}
{this.props.navbarRight}
</div>)
}
}
- src/styles/index.css
body {
margin: 0;
}
.navbar {
display: flex;
text-align: center;
height: 44px;
line-height: 44px;
}
.navbar__left, .navbar__right {
width: 70px;
background: red;
}
.navbar__center {
flex: 1;
background: green;
}
10、context应用场景-跨组件的通信
- 通过props自上而下传递
import React from "react";
export class ProfileHeader extends React.Component {
render() {
return (<div>
<div>{this.props.name}</div>
<div>{this.props.age}</div>
</div>)
}
}
export class Profile extends React.Component {
render() {
return (<div>
{/* 使用jsx特有的属性展开语法替代<ProfileHeader name={this.props.name} age={this.props.age}/> */}
<ProfileHeader {...this.props}/>
</div>)
}
}
export default class App extends React.Component {
render() {
return (<Profile name="黄婷婷" age={18}/>)
}
}
- 通过context传递(类组件)
import React from "react";
// 可以设置默认值
const UserContext = React.createContext({
name: "佟丽娅",
age: 18
})
export class ProfileHeader extends React.Component {
render() {
return (<div>
<div>{this.context.name}</div>
<div>{this.context.age}</div>
</div>)
}
static contextType = UserContext
}
export class Profile extends React.Component {
render() {
return (<ProfileHeader/>)
}
}
export default class App extends React.Component {
render() {
// 通过value属性赋值
return (<UserContext.Provider value={{name: '孟美岐', age: 19}}>
<Profile/>
</UserContext.Provider>)
}
}
- 通过context传递(函数组件)
import React from "react";
const UserContext = React.createContext({
name: "佟丽娅",
age: 18
})
const ThemeContext = React.createContext({
color: "black"
})
function ProfileHeader() {
// 多个context需要嵌套使用非常恶心
return (<UserContext.Consumer>
{value => {
return (<ThemeContext.Consumer>
{theme => {
return (<div style={{color: theme.color}}>
<div>{value.name}</div>
<div>{value.age}</div>
</div>)
}}
</ThemeContext.Consumer>)
}}
</UserContext.Consumer>)
}
export class Profile extends React.Component {
render() {
return (<ProfileHeader/>)
}
}
export default class App extends React.Component {
render() {
return (<UserContext.Provider value={{name: '姜贞羽', age: 20}}>
<ThemeContext.Provider value={{color: 'red'}}>
<Profile/>
</ThemeContext.Provider>
</UserContext.Provider>)
}
}