React(三):脚手架解析、组件分类、生命周期、组件通信
脚手架解析
1.安装脚手架:
npm i create-react-app -g //全局安装
create-react-app --version //查看版本号
2.创建项目:
create-react-app 项目名称
3.目录结构分析:
4.了解PWA
PWA(Progressive Web App,渐进式网页应用)是一种结合了网页和移动应用优点的技术。
特点:
1.可添加至主屏幕,点击图标后挑战至地址栏;
2.通过App Manifest 和 Service Worker实现离线缓存功能,即使没网也可使用一些离线功能
学习网址:渐进式 Web 应用(PWA) | MDN
组件分类
类组件
1.语法
使用ES6的class语法,继承React.Component,包含render()方法和生命周期方法
2.状态管理:通过this.state初始化状态,使用this.setState()更新,支持复杂状态逻辑
3.生命周期:提供完整的生命周期
4.性能优化:通过shouldComponentUpdate或PureComponent优化渲染
5.render函数返回值:
// 1.类组件
class App extends Component {
constructor () {
super()
this.state={
message:"你好呀"
}
}
render() {
// const {message} = this.state
// 1.react元素:通过jsx编写的代码会被编译成React.createElement,所有返回就是一个react元素
// return <h1>{message}</h1>
// 2.返回一个数组
// return ['aaa','bbb','ccc']
// return [
// <h1>元素1</h1>,
// <h1>元素2</h1>,
// <h1>元素3</h1>
// ]
// 3.返回字符串/数字类型
// return '你好呀'
// return 123
// 4.返回布尔类型或者null-界面什么都不展示
return true
}
}
函数组件
1.语法
通过普通函数定义,早期只能渲染UI(无内部状态state),Hooks出现后支持完整功能
2.状态管理:通过useState、useReducer等Hooks管理状态,状态更新更简洁
const [count, setCount] = useState(0); // 函数组件状态
3.无生命周期,可通过useEffect模拟生命周期,如组件挂载、更新、卸载等
4.性能优化:使用React.memo缓存组件,避免不必要的渲染
生命周期
常见生命周期
React内部为了告诉我们当前处于哪些阶段,会对我们组件内部实现的某些函数进行回调,这些函数就是生命周期函数:
1.挂载阶段:
(1)Constructor():不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数
- 初始化内部state
- 为事件绑定实例this
(2)componentDidMount():在组件挂载后(插入 DOM 树中)立即调用
- 依赖于DOM的操作可以在这里进行;
- 在此处发送网络请求就最好的地方;
2.更新阶段:componentDidUpdate()
在更新后会被立即调用,首次渲染不会执行此方法
3.卸载阶段:componentWillUnmount() 会在组件卸载及销毁之前直接调用
如下图所示:
不常见生命周期
- getDerivedStateFromProps:state 的值在任何时候都依赖于 props时使用;该方法返回一个对象来更新state;
- getSnapshotBeforeUpdate:在React更新DOM之前回调的一个函数,可以获取DOM更新前的一些信息(比如说滚动位置)
- shouldComponentUpdate:是否渲染该组件
代码示例:
export class HelloWorld extends React.Component {
// 1.先执行构造方法:constructor
constructor() {
console.log("HelloWorld constructor");
super();
this.state = {
message: "HelloWorld 你好呀"
};
}
changeText() {
this.setState({
message: "你好呀,小橙子",
});
}
// 2.执行render方法
render() {
console.log("HelloWorld render");
const {message} = this.state;
return (
<div>
{message}
<button onClick={() => this.changeText()}>修改文本</button>
</div>
)
}
// 3.组件被渲染到DOM:被挂载到DOM
componentDidMount() {
console.log("HelloWorld componentDidMount");
}
// 4.组件的DOM被更新完成
componentDidUpdate(prevProps, prevState,snapshot) {
console.log("HelloWorld componentDidUpdate",prevProps, prevState,snapshot);
}
//5.组件被从DOM中卸载掉:从DOM移除掉
componentWillUnmount() {
console.log("HelloWorld componentUnmount");
}
// 不常用的生命周期补充
// 该函数可以觉得render函数要不要重新渲染
shouldComponentUpdate() {
return true
}
// 获取在React更新DOM前的一些信息
getSnapshotBeforeUpdate() {
console.log("HelloWorld getSnapshotBeforeUpdate");
return {
scrollPosition:1000
}
}
}
组件通信
父子通信
父传子
Main.jsx-父组件
import React, { Component } from 'react'
import MainBanner from './MainBanner'
import MainProductList from './MainProductList'
import axios from 'axios'
export class Main extends Component {
constructor () {
super ()
this.state = {
banners:[],
productList:[]
}
}
componentDidMount () {
axios.get("http://123.207.32.32:8000/home/multidata").then(
res => {
const banners = res.data.data.banner.list
const recommend = res.data.data.recommend.list
this.setState({
banners,
productList:recommend
})
}
)
}
render() {
const {banners,productList} = this.state
return (
<div className='main'>
<MainBanner banners={banners} title='轮播图' />
<MainBanner/>
<MainProductList productList={productList} />
</div>
)
}
}
export default Main
MainProductList.jsx-子组件
import React, { Component } from 'react'
export class MainProductList extends Component {
// 这部分可以不写→它内部也会将props保存到里面去
/* constructor (props) {
super (props)
console.log(props);
} */
render() {
const {productList} = this.props
return (
<div>
<h1>商品列表</h1>
<ul>
{productList.map((item,index)=>{
return <li key={item.acm}>{item.title}</li>
})}
</ul>
</div>
)
}
}
export default MainProductList
注意:props接收数据类型限制和默认值!!!
MainBanner.jsx-子组件
import React, { Component } from 'react'
import PropTypes from 'prop-types'
export class MainBanner extends Component {
// ES2022开始,可在此处写默认值
static defaultProps = {
banners:[],
title:'默认标题'
}
render() {
const {banners, title} = this.props
return (
<div className='banner'>
<h1>{title}</h1>
<ul>
{banners.map((item,index)=>{
return <li key={item.acm}>{item.title}</li>
})}
</ul>
</div>
)
}
}
MainBanner.propTypes = {
banners:PropTypes.array.isRequired,
title:PropTypes.string
}
// MainBanner.defaultProps = {
// banners:[],
// title:'默认标题'
// }
export default MainBanner
子传父
App.jsx-父组件
import React, { Component } from 'react'
import AddCount from './AddCount'
import SubCount from './SubCount'
export class App extends Component {
constructor () {
super()
this.state={
count:100
}
}
changeCount(n) {
this.setState({
count:this.state.count + n
})
}
render() {
const {count} = this.state
return (
<div>
<h2>当前计数:{count}</h2>
<AddCount addClick={(n) => this.changeCount(n)}/>
<SubCount subClick={(n) => this.changeCount(n)}/>
</div>
)
}
}
export default App
AddCount.jsx-子组件
import React, { Component } from 'react'
import PropTypes from 'prop-types'
export class AddCount extends Component {
addCount(n) {
const click = this.props.addClick
click(n)
}
render() {
return (
<div>
<button onClick={e => this.addCount(1)}>+1</button>
<button onClick={e => this.addCount(5)}>+5</button>
<button onClick={e => this.addCount(10)}>+10</button>
</div>
)
}
}
AddCount.propTypes = {
addCount: PropTypes.func.isRequired
}
export default AddCount
SubCount.jsx-子组件
import React, { Component } from 'react'
export class SubCount extends Component {
SubCount(n) {
this.props.subClick(n)
}
render() {
return (
<div>
<button onClick={e => this.SubCount(-1)}>-1</button>
<button onClick={e => this.SubCount(-5)}>-5</button>
<button onClick={e => this.SubCount(-10)}>-10</button>
</div>
)
}
}
export default SubCount
插槽用法
1.通过props.children实现插槽
2.通过props直接实现插槽
App.jsx
export class App extends Component {
render() {
return (
<div>
{/* 1.使用children实现插槽 */}
<NavBar>
{/*
注意:
当这里放多个时——children:[button,h2,i]
当这里只有一个时,children:button——children就是该元素
弊端:通过索引值获取传入的元素很容易出错,不能精准的获取传入的原生
*/}
<button>按钮</button>
<h2>我是标题</h2>
<i>斜体文字</i>
</NavBar>
{/* 2.使用props实现插槽-父传子-推荐使用该方案 */}
<NavBarr
leftSlot={<button>按钮</button>}
centerSlot={<h2>我是标题</h2>}
rightSlot={<i>斜体文字</i>}
>
</NavBarr>
</div>
)
}
}
NavBar.jsx
import React, { Component } from 'react'
export class NavBar extends Component {
render() {
const {children} = this.props
return (
<div className='nav-bar'>
<div className="left">{children[0]}</div>
<div className="center">{children[1]}</div>
<div className="right">{children[2]}</div>
</div>
)
}
}
export default NavBar
NavBarr.jsx
import React, { Component } from 'react'
export class NavBarr extends Component {
render() {
const {leftSlot,centerSlot,rightSlot} = this.props
return (
<div className='nav-bar'>
<div className="left">{leftSlot}</div>
<div className="center">{centerSlot}</div>
<div className="right">{rightSlot}</div>
</div>
)
}
}
export default NavBarr
综合案例
App.jsx
import React, { Component } from 'react';
import { TabControl } from './TabControl.jsx';
import './style.css'
export class App extends Component {
constructor() {
super();
this.state = {
titles: ["流行", "新款", "精选"],
activeIndex:0
};
}
changeTab(index) {
this.setState({
activeIndex:index
})
}
render() {
const { titles,activeIndex } = this.state;
return (
<div>
<TabControl titles={titles} activeClick={index => this.changeTab(index)} />
<div className='tab-content'>{titles[activeIndex]}</div>
</div>
);
}
}
export default App;
TabControl.jsx
import React, { Component } from 'react'
export class TabControl extends Component {
constructor () {
super()
this.state = {
tabActive:0
}
}
tabActive(index) {
this.setState({
tabActive:index
})
this.props.activeClick(index)
}
render() {
const { titles } = this.props;
const { tabActive } = this.state;
return (
<div className='tab-control'>
{titles.map((item,index) =>
<div
key={index}
className={tabActive === index ? 'active' : ''}
onClick={() => this.tabActive(index)}
>{item}</div>)}
</div>
)
}
}
export default TabControl
非父子通信
Context API
通过 Provider 和 Consumer 跨层级共享数据,避免逐层传递
uesr-context.js
import React from "react";
// 1.创建一个Context
const UserContext = React.createContext()
export default UserContext
App.jsx
import React, { Component } from 'react'
import Home from './Home'
import UserContext from './context/uesr-context';
export class App extends Component {
constructor() {
super();
this.state = {
info:{name:'why',age:18}
}
}
render() {
const {info} = this.state;
return (
<div>
<h2>App</h2>
{/* 1.给Home传递数据 */}
{/* <Home name="why" age={18}/> */}
{/* <Home name={info.name} age={info.age}/> */}
{/* 官方文档:若已经有一个props对象,可使用展开运算符传递整个props对象 */}
{/* <Home {...info}/> */}
{/* 2.普通的Home */}
{/* 第二步:通过Context中的Provider中的value属性为后代提供数据 */}
<UserContext.Provider value={{name:'why',age:18}}>
<Home {...info}/>
</UserContext.Provider>
</div>
)
}
}
export default App
HomeInfo.jsx-孙组件
import React, { Component } from 'react'
import InfoContext from './context/theme-context'
import UserContext from './context/uesr-context';
export class HomeInfo extends Component {
render() {
// 第四步:获取数据,并使用
console.log(this.context);
return (
<div>
<h1>HomeInfo</h1>
<p>ThemeContext:{this.context.color}</p>
<p>{this.context.size}</p>
<UserContext.Consumer>
{(value)=><p>{value.name}</p>}
</UserContext.Consumer>
</div>
)
}
}
// 第三步:设置组件contextType的类型为某一个Context
HomeInfo.contextType = InfoContext
export default HomeInfo
EventBus事件总线
event-bus.js
import {HYEventBus} from "hy-event-store"
// 1.创建一个都能访问的事件总线
const eventBus = new HYEventBus()
export default eventBus
App.jsx
import React, { Component } from 'react'
import Home from './Home'
import eventBus from './utils/event-bus'
export class App extends Component {
constructor() {
super();
this.state = {
name: 'why',
age: 18,
gender: '男',
info:{bookname:'jsbook',price:100}
}
}
componentDidMount () {
// eventBus.on('bannerPrev', (name,age,gender) => {
// console.log(name,age,gender);
// this.setState({name,age,gender})
// })
//第三个参数:传递给回调函数的上下文(即This对象),用于指定函数内部的this指向
eventBus.on('bannerPrev', this.bannerPrevClick,this)
eventBus.on('bannerNext', this.bannerNextClick,this)
}
// 将其定义成一个函数,抽取出去
bannerPrevClick(name,age,gender) {
this.setState({name,age,gender})
}
bannerNextClick(info) {
this.setState({
info:{...info}
})
}
// 当该子组件销毁时,需要移除监听事件
componentWillUnmount () {
eventBus.off('bannerPrev',this.bannerPrevClick)
}
render() {
const {name,age,gender,info} = this.state;
return (
<div>App-{name}-{age}-{gender}
<Home />
<h2>{info.bookname}-{info.price} </h2>
</div>
)
}
}
export default App
HomeBanner.jsx-孙组件
import React, { Component } from 'react'
import eventBus from './utils/event-bus';
export class HomeBanner extends Component {
prevClick() {
console.log('上一个');
eventBus.emit('bannerPrev',"Lily",22,"女")
}
nextClick() {
console.log('下一个');
eventBus.emit('bannerNext',{bookname:'《算法导论》',price:99})
}
render() {
return (
<div>
HomeBanner
<button onClick={e => this.prevClick()}>上一个</button>
<button onClick={e => this.nextClick()}>下一个</button>
</div>
)
}
}
export default HomeBanner