分而治之的思想
任何一个人处理信息的逻辑能力都是有限的,所以,当面对一个非常复杂的问题时,我们不太可能一次性搞定一大堆的内容。但是,我们人有一种天生的能力,就是将问题进行拆解。如果将一个复杂的问题,拆分成很多个可以处理的小问题,再将其放在整体当中,你会发现大的问题也会迎刃而解。其实这里的思想就是分而治之的思想,分而治之是软件工程的重要思想,是复杂系统开发和维护的基石,而前端目前的模块化和组件化都是基于分而治之的思想。
什么是组件化开发?
组件化也是类似分而治之的思想。如果我们将一个页面中所有的处理逻辑全 部放在一起,处理起来就会变得非常复杂, 而且不利于后续的管理以及扩展。但如果,我们讲一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部 分独立的功能,那么之后整个页面的管理 和维护就变得非常容易了。
我们需要通过组件化的思想来思考整个应用程序
- 我们将一个完整的页面分成很多个组件
- 每个组件都用于实现页面的一个功能块
- 每一个组件又可以进行细分
- 组件本身又可以在多个地方进行复用
React的组件化
React的组件相对于Vue更加的灵活和多样,按照不同的方式可以分成很多类组件
- 根据组件的定义方式,可以分为:函数组件(Functional Component )和类组件(Class Component)
- 根据组件内部是否有状态需要维护,可以分成:无状态组件(Stateless Component )和有状态组件(Stateful Component)
- 根据组件的不同职责,可以分成:展示型组件(Presentational Component)和容器型组件(Container Component)
这些概念有很多重叠,但是他们最主要是关注数据逻辑和UI展示的分离
- 函数组件、无状态组件、展示型组件主要关注UI的展示
- 类组件、有状态组件、容器型组件主要关注数据逻辑
当然还有很多组件的其他概念:比如异步组件、高阶组件等。
类组件
import React, { Component } from 'react'
export default class App extends Component {
constructor(){
super();
this.state = {
message:'HelloWorld'
}
}
render() {
return (
<div>
<h2>{this.state.message}</h2>
</div>
)
}
}
类组件定义有如下的要求:
- 组件的名称是大写字符开头(无论类组件还是函数组件)
- 类组件需要继承自 React.Component
- 类组件必须实现render函数
使用class定义一个组件:
- constructor是可选的,我们通常在constructor中初始化一些数据
- this.state中维护的就是我们组件内部的数据
- render() 方法是 class 组件中唯一必须实现的方法
函数式组件
import React from 'react'
/**
* 函数式组件的特点:
* 1.没有this对象
* 2.没有内部的状态
*/
export default function App() {
return (
<div>App</div>
)
}
函数组件是使用function来进行定义的函数,只是这个函数会返回和类组件中render函数返回一样的内容。函数式组件的缺点是没有状态(state)。
React的生命周期
很多的事物都有从创建到销毁的整个过程,这个过程称之为是生命周期React组件也有自己的生命周期,了解组件的生命周期可以让我们在最合适的地方完成自己想要的功能. 生命周期是一个抽象的概念,在生命周期的整个过程,分成了很多个阶段:
- 比如装载阶段(Mount),组件第一次在DOM树中被渲染的过程
- 比如更新过程(Update),组件状态发生变化,重新更新渲染的过程
- 比如卸载过程(Unmount),组件从DOM树中被移除的过程
React内部为了告诉我们当前处于哪些阶段,会对我们组件内部实现的某些函数进行回调,这些函数就是生命周期函数。
- 比如实现
componentDidMount
函数:组件已经挂载到DOM上时,就会回调 - 比如实现
componentDidUpdate
函数:组件已经发生了更新时,就会回调 - 比如实现
componentWillUnmount
函数:组件即将被移除时,就会回调
我们可以在这些回调函数中编写自己的逻辑代码,来完成自己的需求功能。
我们谈React生命周期时,主要谈的类的生命周期,因为函数式组件是没有生命周期函数的。
图中Mounting挂载阶段(第一轮执行):
import React, { Component } from 'react'
export default class App extends Component {
constructor(){
super()
console.log('执行了组件的constructor----这是最早执行的 step1');
}
render() {
console.log('执行了组件的render函数------step2');
return (
<div>
{/* 挂在这个组件----------step3 */}
我是App组件
</div>
)
}
componentDidMount(){
console.log('执行了componentDidMount方法-------step3');
}
}
图中Update阶段(更新组件内容的时候):
import React, { Component } from 'react'
export default class App extends Component {
constructor(){
super()
console.log('执行了constructor---------');
this.state = {
counter:0
}
}
render() {
console.log('执行了render---------');
return (
<div>
<h2>当前计数:{this.state.counter}</h2>
<button onClick = {e => this.increament(e)}>+1</button>
</div>
)
}
increament(e){
this.setState({
counter : this.state.counter + 1
})
}
componentDidMount(){
console.log('执行了组件的componentDidMount方法');
}
componentDidUpdate(){
console.log('执行了组件的componentDidUpdate方法');
}
}
一开始控制台打印的结果是这样的,这是挂载的时候做的操作。
我们点击清空后,再点击 +1就会发现控制台打印的结果变了。
我们无论是执行setState,forceUpdate还是New props我们都会重新执行render,再执行这个Didupdate生命周期函数。
图中unmounting(卸载)的情况
import React, { Component } from 'react'
class Cpn extends Component{
render(){
return <h2>我是Cpn组件</h2>
}
componentWillUnmount(){
console.log('调用了componentWillUnmount方法');
}
}
export default class App extends Component {
constructor(){
super()
console.log('执行了constructor---------');
this.state = {
counter:0,
isShow:true,
}
}
render() {
console.log('执行了render---------');
return (
<div>
我是App组件
{this.state.isShow && <Cpn></Cpn>}
</div>
)
}
changeCpnShow(){
this.setState({
isShow:!this.state.isShow
})
}
componentDidMount(){
console.log('执行了组件的componentDidMount方法');
}
componentDidUpdate(){
console.log('执行了组件的componentDidUpdate方法');
}
}
我们点击切换的按钮,就会打印如下的信息。
constructor生命周期函数的作用:
一般来说,就拿它来干两件事:
- 通过给this.state 赋值对象来初始化内部的state
- 为事件绑定实例(this)
componentDidMount生命周期函数的作用:
omponentDidMount() 会在组件挂载后(插入 DOM 树中)立即调用。 componentDidMount中通常进行哪里操作呢?
- 依赖于DOM的操作可以在这里进行
- 在此处发送网络请求就最好的地方(官方建议)
- 可以在此处添加一些订阅(会在componentWillUnmount取消订阅)
componentDidUpdate生命周期函数的作用:
componentDidUpdate() 会在更新后会被立即调用,首次渲染不会执行此方法。
- 当组件更新后,可以在此处对 DOM 进行操作
- 如果你对更新前后的 props 进行了比较,也可以选择在此处进行网 络请求;(例如,当 props 未发生变化时,则不会执行网络请求)
componentWillUnmount生命周期函数的作用
componentWillUnmount() 会在组件卸载及销毁之前直接调用。
在此方法中执行必要的清理操作。
- 例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等
不常用的生命周期函数
除了上面介绍的生命周期函数之外,还有一些不常用的生命周期函数(我们可以在官方查看到)react.docschina.org/docs/react-…
组件的嵌套(数据传递与通信)
如果我们一个应用程序将所有的逻辑都放在一个组件中,那么这个组件就会变成非常的臃肿和难以维护。所以组件化的核心思想应该是对组件进行拆分,拆分成一个个小的组件。再将这些组件组合嵌套在一起,最终形成我们的应用程序。
我们看如图的嵌套组件,它们存在以下的关系:
- App组件是Header、Main、Footer组件的父组件
- Main组件是Banner、ProductList组件的父组件
import React, { Component } from 'react';
// Header
function Header() {
return <h2>我是Header组件</h2>
}
// Main
function Banner() {
return <h3>我是Banner组件</h3>
}
function ProductList() {
return (
<ul>
<li>商品列表1</li>
<li>商品列表2</li>
<li>商品列表3</li>
<li>商品列表4</li>
<li>商品列表5</li>
</ul>
)
}
function Main() {
return (
<div>
<Banner/>
<ProductList/>
</div>
)
}
// Footer
function Footer() {
return <h2>我是Footer组件</h2>
}
export default class App extends Component {
render() {
return (
<div>
<Header/>
<Main/>
<Footer/>
</div>
)
}
}
通信之组件父传子
在类组件中实现
import React, { Component } from 'react';
class ChildCpn extends Component {
constructor(props) {
super();
this.props = props
}
render() {
const {name, age, height} = this.props;
return (
<h2>子组件展示数据: {name + " " + age + " " + height}</h2>
)
}
}
export default class App extends Component {
render() {
return (
<div>
<ChildCpn name="why" age="18" height="1.88"/>
<ChildCpn name="kobe" age="40" height="1.98"/>
</div>
)
}
}
这里的constructor是由props这个参数的,我们再通过this.props = props 就可以在render里拿到父组件传过来的内容。
但是我们有点麻烦,每次都要实现这个constructor()函数。我们其实可以不用实现。要知道为什么可以省略,就得看点原理性的东西。
一步一步来,先看下面的图,我们可以把操作简化成如代码1所示:
//代码1
constructor(props){
super(props)
}
这里我们还是用this.props来解构而不是super.props。 这是一个简略的写法,其实这里整个constructor()函数都可以不写。
我们从源码处来看,为什么简化成代码1可以。
我们打开React源码处查看。
我们所有的组件都是继承于整个Component的,而这个组件里做了this.props = props这个操作。
此外,子类(派生类)默认的构造方式就是如代码1所示,所以代码1不写也是可以的。 最后我们得到
import React, { Component } from 'react';
class ChildCpn extends Component {
render() {
const {name, age, height} = this.props;
return (
<h2>子组件展示数据: {name + " " + age + " " + height}</h2>
)
}
}
export default class App extends Component {
render() {
return (
<div>
<ChildCpn name="why" age="18" height="1.88"/>
<ChildCpn name="kobe" age="40" height="1.98"/>
</div>
)
}
}
这里有一个问题,如果我们的代码是这个样子的。
这里的展示会不会有问题?
但是很奇怪,这里的却可以正常显示。
我们猜测一下,会不会是super帮我们偷偷保存了。(父类帮我们偷偷保存了)
可是这里打印的是undefined
父类没保存,但是render和componentDidMount()可以整打印这个值,那压根没保存为什么会有值呢?
要解决这个问题,我们就必须来读一下源码,位置在package底下。(这个包用于将 React 组件渲染成纯 JavaScript 对象)
如果你不知道这个文件是干啥的,可以去官方文档里看,如图
这里传入的就是那个ReactElement
再往下翻
看一下我们这个instance是否存在,如果存在我们就做个更新操作。(刚开始这个组件不存在)
- 如果不存在再判断是否是构造器
但是创建出来依然没有赋值,我们接着找到对它赋值的地方。
这个也是React的一个骚操作了。它可能就是担心有些人写类组件的时候,忘了保存。如果你做了保存,它又保存了一遍。让props一定能传给子组件。
在函数式组件中实现
函数式组件就非常简单了,因为函数式组件是没有this的。
import React, { Component } from 'react';
function ChildCpn(props) {
const { name, age, height } = props;
return (
<h2>{name + age + height}</h2>
)
}
export default class App extends Component {
render() {
return (
<div>
<ChildCpn name="why" age="18" height="1.88" />
<ChildCpn name="kobe" age="40" height="1.98" />
</div>
)
}
}
父传子的属性验证
对于传递给子组件的数据,有时候我们可能希望进行验证,特别是对于大型项目来说。当然,如果你项目中默认集成了Flow
或者TypeScript
,那么直接就可以进行类型验证。但是,即使我们没有使用Flow或者TypeScript,也可以通过 prop-types 库来进行参数验证。
- 从 React v15.5 开始,React.PropTypes 已移入另一个包中:prop-types 库
下面的代码子组件我们采用函数式组件
import React, { Component } from 'react';
import PropTypes from 'prop-types';
function ChildCpn(props) {
const { name, age, height } = props;
const { names } = props;
return (
<div>
<h2>{name + age + height}</h2>
<ul>
{
names.map((item, index) => {
return <li key={item}>{item}</li>
})
}
</ul>
</div>
)
}
//属性限制格式
ChildCpn.propTypes = { //这里这个p是小写,还要记住不要一不小心敲成prototype
name: PropTypes.string.isRequired, //isRequired表示必须要传
age: PropTypes.number,
height: PropTypes.number,
names: PropTypes.array
}
export default class App extends Component {
render() {
return (
<div>
<ChildCpn name="harry" age={18} height={1.88} names={["abc", "cba"]}/>
</div>
)
}
}
你如果不按格式来就报错。开发中如果要做复杂的验证,还是直接用Typescript。
此外PropTypes也可以提供默认值,加到下面就行了。
ChildCpn.defaultProps = {
name: "why",
age: 30,
height: 1.98,
names: ["aaa", "bbb"]
}
当然我们也可以采用es6中的class fields写法把proTypes写到类里面,前面加上static标记这个对象是静态对象。(这里要用类组件)
import React, { Component } from 'react'
import ProTypes from 'prop-types'
class ChildCpn extends Component{
render(){
const {name,age,names} = this.props
return (
<div>
<div>{name + age}</div>
<ul>
{
names.map((item,index) => {
return <li key={item}>{item}</li>
})
}
</ul>
</div>
)
}
//使用es6的 class fields写法 来进行属性验证
static proTypes = {
name:ProTypes.string.isRequired
}
static defaultTypes = {
name:'lerbo',
age:41,
height:2.16,
names:["ccc","bbb","aaa"]
}
}
export default class App extends Component {
constructor(){
super()
this.state = {
names : ["abc","cba","ccc"]
}
}
render() {
return (
<div>
<ChildCpn name = "harry" age = {18 * 5} names = {this.state.names}></ChildCpn>
</div>
)
}
}
子组件传递给父类
子组件发生了某一个事件,我们需要将事件传递给父类里。这在开发中是很常见的。
- Vue是通过自定义事件来完成的。
- React依然是通过props传递一个东西,只不过父组件给子组件传的是一个回调函数,在子组件中调用这个函数即可。
import React, { Component } from 'react';
class CounterButton extends Component {
render() {
const {increment} = this.props;
// console.log(increment);
return <button onClick = {increment}>+1</button>
}
}
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
render() {
return (
<div>
<h2>当前计数: {this.state.counter}</h2>
{/* 想办法把increament函数传到子组件里去 */}
<CounterButton increment = {this.increment} />
</div>
)
}
increment() {
console.log('increament被调用了~');
// this.setState({
// counter: this.state.counter + 1
// })
}
}
逻辑是这样的
当我们点击 +1 的按钮
如果我们想要让计数 +1 上面的代码会出现this undefined的问题。 我们需要解决这个this的问题。
- 可以传入的时候this.incremen.bind(this)
- 也可以定义increament函数,写成箭头函数表达式
- 传入一个箭头函数(建议采用这个方案)
案例来巩固组件通信的知识
我们的样式
.tab-control {
display: flex;
height: 44px;
line-height: 44px;
}
.tab-item {
flex: 1;
text-align: center;
}
.tab-item span {
padding: 5px 8px;
}
.tab-item.active {
color: red;
}
.tab-item.active span {
border-bottom: 3px solid red;
}
先来把tabbar基础搭建出来
//App.js
import React, { Component } from 'react'
import TabControl from './TabControl'
export default class App extends Component {
constructor(props){
super(props)
this.titles = ['新款','流行','男装']
this.state = {
}
}
render() {
return (
<div>
<TabControl titles = {this.titles}></TabControl>
</div>
)
}
}
//TabBar
import React, { Component } from 'react'
export default class TabControl extends Component {
constructor(props){
super(props)
}
render() {
const {titles} = this.props
return (
<div className='tab-control'>
{
titles.map((item,index) =>{
return <div className='tab-item' key={item}>{item}</div>
})
}
</div>
)
}
}
我们得记录哪个被点击,并且点击的加上红色。
import React, { Component } from 'react'
export default class TabControl extends Component {
constructor(props){
super(props)
this.state = {
cuurentIndex:0,
}
}
render() {
const {titles} = this.props
const {cuurentIndex} = this.state
return (
<div className='tab-control'>
{
titles.map((item,index) =>{
return(
<div
className={'tab-item ' + (cuurentIndex === index ? "active" : "") }
key={item}
onClick = {e => this.itemClick(index)}
>
{item}
</div>
)
})
}
</div>
)
}
itemClick(index){
this.setState({
cuurentIndex:index
})
}
}
优化样式后,我们想要在tabbar下面出现对应文本,其实很简单,我们只要告诉父容器要显示的内容就行了。
我们可以将tabbar的索引值传给App,具体代码如下
//App
import React, { Component } from 'react'
import TabControl from './TabControl'
export default class App extends Component {
constructor(props){
super(props)
this.titles = ['新款','流行','男装']
this.state = {
currentTitle:'新款',
}
}
render() {
const {currentTitle} = this.state
return (
<div>
<TabControl itemClick = {index => this.itemClick(index)} titles = {this.titles}></TabControl>
<h2>{currentTitle}</h2>
</div>
)
}
itemClick(index){
this.setState({
currentTitle:this.titles[index]
})
}
}
//TabControl
import React, { Component } from 'react'
export default class TabControl extends Component {
constructor(props){
super(props)
this.state = {
cuurentIndex:0,
}
}
render() {
const {titles} = this.props
const {cuurentIndex} = this.state
return (
<div className='tab-control'>
{
titles.map((item,index) =>{
return(
<div
className={'tab-item ' + (cuurentIndex === index ? "active" : "") }
key={item}
onClick = {e => this.itemClick(index)}
>
<span >{item}</span>
</div>
)
})
}
</div>
)
}
itemClick(index){
this.setState({
cuurentIndex:index
})
const {itemClick} = this.props
itemClick(index)
}
}
这样就实现了整个功能
React中实现Vue中的插槽(slot)功能
React压根就不需要插槽整个东西,jsx非常灵活。
我们先来演示简单的情况:
在源码中:
整个props传到子组件里面了。
我们子组件(待插入组件)里的children(组件里的东西)
所以子组件(待插入组件)里有props.children,里面存放着预留的插槽。
这样子,子组件里就有了我们在父组件中传入的插槽了。
我们一般把样式加在待插入的组件里面。
代码如下
//App.js
import React, { Component } from 'react'
import NavBar from './NavBar'
export default class App extends Component {
render() {
return (
<div>
<NavBar name = "">
<span>aaa</span>
<strong>bbb</strong>
<a href="/#">浏览一下</a>
</NavBar>
</div>
)
}
}
//NavBar.js
import React, { Component } from 'react'
export default class NavBar extends Component {
render() {
return (
<div className='nav-bar'>
<div className='nav-left'>{this.props.children[0]}</div>
<div className='nav-center'>{this.props.children[1]}</div>
<div className='nav-right'>{this.props.children[2]}</div>
</div>
)
}
}
这种children方式也有它的问题,比如
- 顺序不能乱
一般只插入一个用这个。
React中实现类似Vue的具名插槽
具名插槽可以精准的拿到每一个属性。 React在这方面比Vue灵活多了。
//App.js
import React, { Component } from 'react'
import NavBar from './NavBar'
export default class App extends Component {
render() {
const leftJsx = <span>aaa</span>;
return (
<div>
<NavBar leftSlot = {leftJsx}
centerSlot = {<strong>bbb</strong>}
rightSlot = {<a href="/#">点击</a>}
>
</NavBar>
</div>
)
}
}
//NavBar.js
import React, { Component } from 'react'
export default class NavBar extends Component {
render() {
const {leftSlot,centerSlot,rightSlot} = this.props; //先拿到插槽的东西
return (
<div className='nav-bar'>
<div className='nav-left'>{leftSlot}</div>
<div className='nav-center'>{centerSlot}</div>
<div className='nav-right'>{rightSlot}</div>
</div>
)
}
}
跨组件通信 - 原生实现
跨组件通信,这里的我的理解就是比如子组件和爷爷组件,或者祖爷爷组件通信。
我们先来演示最简单的,也是最容易想到的,一层一层传递。
// 跨组件就是类似曾孙子和爷爷通信.
import React, { Component } from 'react'
function ProfileHeader(props){
return(
<div>
<h2>用户的昵称:{props.nickname}</h2>
<h2>用户等级:{props.level}</h2>
</div>
)
}
function Profile(props){
return(
<div>
<ProfileHeader nickname = {props.nickname} level = {props.level}></ProfileHeader>
<ul>
<li>设置1</li>
<li>设置2</li>
<li>设置3</li>
<li>设置4</li>
</ul>
</div>
)
}
export default class App extends Component {
constructor(props){
super(props)
this.state = {
nickname: "harry",
level:99
}
}
render() {
const {nickname,level} = this.state
return (
<div>
<Profile nickname = {nickname} level = {level}></Profile>
</div>
)
}
}
这个其实太麻烦了!
其实在Vue和React都有一个用法,叫做属性展开
。在React官网可以找到这个东西。
我们来简化刚才的代码
其实state也可以这么展开。
跨组件通信 - Context
Context的应用场景:
- 非父子组件数据的共享。
- 在开发中,比较常见的数据传递方式是通过props属性自上而下(由父到子)进行传递。
- 但是对于有一些场景:比如一些数据需要在多个组件中进行共享(地区偏好、UI主题、用户登录状态、用户信息等)。
- 如果我们在顶层的App中定义这些信息,之后一层层传递下去,那么对于一些中间层不需要数据的组件来说,是一种冗余的 操作。
- 但是,如果层级更多的话,一层层传递是非常麻烦,并且代码是非常冗余的。
- React提供了一个API:Context
- Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props
- Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言
Context相关API
- React.createContext
- 创建一个需要共享的Context对象
- 如果一个组件订阅了Context,那么这个组件会从离自身最近的那个匹配的 Provider 中读取到当前的context值
- efaultValue是组件在顶层查找过程中没有找到对应的Provider,那么就使用默认值
- Context.Provider
- 每个 Context 对象都会返回一个Provider React 组件,它允许消费组件订阅 context 的变化
- Provider 接收一个value 属性,传递给消费组件
- 一个Provider 可以和多个消费组件有对应关系
- 多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据
- 当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染
- Class.contextType
- 挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象
- 这能让你使用 this.context 来消费最近 Context 上的那个值
- 你可以在任何生命周期中访问到它,包括 render 函数中
- Context.Consumer
- 这里,React 组件也可以订阅到 context 变更。这能让你在 函数式组件 中完成订阅 context
- 这里需要 函数作为子元素(function as child)这种做法
- 这个函数接收当前的 context 值,返回一个 React 节点
Context代码演练:
以类组件为例
第一步我们要设置共享(UserContext里的值是默认值)
第二步,获取共享的值,这里我们通过阅读源码得知,每个组件都有一个Context对象
所以这里我们这么操作。这里我们得熟练这个操作,这是redux的基础。
函数式组件就不一样了,有点类似flutter的做法。
如果有多个context
// 跨组件就是类似曾孙子和爷爷通信.
import React, { Component } from 'react'
//创建Context对象
const UserContext = React.createContext({
nickname: "aaaa",
level: -1
})
const ThemeContext = React.createContext({
color: "black"
})
function ProfileHeader(){
return(
<UserContext.Consumer>
{
value => {
return(
<ThemeContext.Consumer>
{
theme => {
return(
<div>
<h2>用户昵称:{value.nickname}</h2>
<h2>用户等级:{value.level}</h2>
<h2>颜色:{theme.color}</h2>
</div>
)
}
}
</ThemeContext.Consumer>
)
}
}
</UserContext.Consumer>
)
}
// ProfileHeader.contextType = UserContext
// ProfileHeader.contextType = ThemeContext
function Profile(props){
return(
<div>
<ProfileHeader ></ProfileHeader>
<ul>
<li>设置1</li>
<li>设置2</li>
<li>设置3</li>
<li>设置4</li>
</ul>
</div>
)
}
export default class App extends Component {
constructor(props){
super(props)
this.state = {
nickname: "harry",
level:99
}
}
render() {
const {nickname,level} = this.state
return (
<div>
<UserContext.Provider value = {this.state}>
<ThemeContext.Provider value = { {color:'red'} }>
<Profile></Profile>
</ThemeContext.Provider>
</UserContext.Provider>
</div>
)
}
}
这里要注意