React备忘录(基于B站张天禹视频 网址 https://www.bilibili.com/video/BV1wy4y1D7JT)

685 阅读15分钟

虚拟 DOM 与真实 DOM

    关于虚拟DOM:
				1. 本质是Object类型的对象(一般对象)
				2. 虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在用,无需真实DOM上那么多的属性。
				3. 虚拟DOM最终会被React转化为真实DOM,呈现在页面上。

jsx 语法规则:

					1. 定义虚拟DOM时,不要写引号。
					2. 标签中混入 JS表达式 时要用{}。
					3. 样式的类名不要用class,要用className。
					4. 内联样式,要用style={{key:value}}的形式去写。有-的 要用小驼峰写
					5. 只有一个根标签
							<Fragment key={1}>
								<input type="text"/>
								<input type="text"/>
							</Fragment>
							解析时会自动去除 Fragment 标签, Fragment标签只能有一个属性  (key)
					6. 标签必须闭合
					7. 标签首字母
							(1).若小写字母开头,则将该标签转为html中同名元素,若html中无该标签对应的同名元素,则报错。
							(2).若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。

一定注意区分:【js 语句(代码)】与【js 表达式】

1. 表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
			下面这些都是表达式:
					(1). a
					(2). a+b
					(3). demo(1)
					(4). arr.map()
					(5). function test () {}
2.语句(代码):
			下面这些都是语句(代码):
					(1).if(){}
					(2).for(){}
					(3).switch(){case:xxxx}

函数组件

  1.创建函数式组件
	function MyComponent(){
		console.log(this); //此处的this是undefined,因为babel编译后开启了严格模式
		return <h2>我是用函数定义的组件<h1>(适用于【简单组件】的定义)</h1></h2>
	}
	//2.渲染组件到页面
	ReactDOM.render(<MyComponent></MyComponent>,document.getElementById('test'))
	/*
		执行了ReactDOM.render(<MyComponent/>.......之后,发生了什么?
				1.React解析组件标签,找到了MyComponent组件。
				2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。
	*/

函数组件 hooks

//count为值setCount为修改值的方法,后面()里的内容为初始化的值
const [count,setCount] = React.useState(0)
//创建ref 和 类式组件使用方法相同
const myRef = React.useRef()
//生命周期 函数体内表示 componentDidMount 和 componentDidUpadte。
	函数的返回值是一个函数  对应 componentWillUnmount 生命周期
	第二个参数表示监视的属性,不写代表全部监视,空数组表示都不监视,数组里的表示需要监视的。
React.useEffect(()=>{
	let timer = setInterval(()=>{
		setCount(count => count+1 )
	},1000)
	return ()=>{
		clearInterval(timer)
	}
},[])

类式组件

	//1.创建类式组件
	class MyComponent extends React.Component {
		render(){
			//render是放在哪里的?—— MyComponent的原型对象上,供实例使用。
			//render中的this是谁?—— MyComponent的实例对象 <=> MyComponent组件实例对象。
			console.log('render中的this:',this);
			return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
		}
	}
	//2.渲染组件到页面
	ReactDOM.render(<MyComponent/>,document.getElementById('test'))
	/*
		执行了ReactDOM.render(<MyComponent/>.......之后,发生了什么?
				1.React解析组件标签,找到了MyComponent组件。
				2.发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。
				3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。
	*/

对 props 进行限制

			引入prop-types,用于对组件标签属性进行限制

			函数组件和类式组件对组件标签属性进行限制(通用)
			//对标签属性进行类型、必要性的限制
			Person.propTypes = {
			name:PropTypes.string.isRequired, //限制 name 必传,且为字符串
			sex:PropTypes.string,//限制 sex 为字符串
			age:PropTypes.number,//限制 age 为数值
			speak:PropTypes.func,//限制 speak 为函数
			}
			//指定默认标签属性值
			Person.defaultProps = {
			sex:'男',//sex 默认值为男
			age:18 //age 默认值为 18
			}

类式组件可以直接在 Class 里使用

			static propTypes = {
				name:PropTypes.string.isRequired, //限制name必传,且为字符串
				sex:PropTypes.string,//限制sex为字符串
				age:PropTypes.number,//限制age为数值
			}

			//指定默认标签属性值
			static defaultProps = {
				sex:'男',//sex默认值为男
				age:18 //age默认值为18
			}

ref

  1. 类式组件

		```jsx
					<input ref="input1" type="text" placeholder="点击按钮提示数据"/>
						showData = ()=>{
							const {input1} = this.refs
							alert(input1.value)
						}

									<input ref={c => this.input1 = c } type="text" placeholder="点击按钮提示数据"/>
											showData = ()=>{
							const {input1} = this
							alert(input1.value)
						}

							<input ref={this.saveInput} type="text" />
											saveInput = (c) => {
							this.input1 = c;
							console.log("@", c);
						};

									<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>

								myRef = React.createRef()
											showData = ()=>{
							alert(this.myRef.current.value);
						}
		```

 2. 函数式组件

			```jsx
			<input type="text" ref={myRef} />;
			const myRef = React.useRef();
			```

事件处理

(1).通过 onXxx 属性指定事件处理函数(注意大小写) a.React 使用的是自定义(合成)事件, 而不是使用的原生 DOM 事件 —————— 为了更好的兼容性 b.React 中的事件是通过事件委托方式处理的(委托给组件最外层的元素) ————————为了高效 (2).通过 event.target 得到发生事件的 DOM 元素对象 ——————————不要过度使用 ref

受控组件和非受控组件

在 React 中,所谓受控组件和非受控组件,是针对表单而言的。 React 组件的数据渲染是否被调用者传递的props完全控制,控制则为受控组件,否则非受控组件。

状态提升

如果有多个组件共享一个数据,把这个数据放到共同的父级组件中来管理

高阶函数_函数柯里化

			/*
				高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
								1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
								2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
								常见的高阶函数有:Promise、setTimeout、arr.map()等等

				函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
					function sum(a){
						return(b)=>{
							return (c)=>{
								return a+b+c
							}
						}
					}
				*/

生命周期

1. 初始化阶段: 由ReactDOM.render()触发---初次渲染
					1.	constructor()
					2.	componentWillMount()
					3.	render()
					4.	componentDidMount() =====> 常用
									一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
2. 更新阶段: 由组件内部this.setSate()或父组件render触发
					1.	shouldComponentUpdate()
					2.	componentWillUpdate()
					3.	render() =====> 必须使用的一个
					4.	componentDidUpdate()
3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
					1.	componentWillUnmount()  =====> 常用
									一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息

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()  =====> 常用
							一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息

key 的 作用

经典面试题: 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:
									会产生错误DOM更新 ==> 界面有问题。
					3. 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,
						仅用于渲染列表用于展示,使用index作为key是没有问题的。
3. 开发中如何选择key?:
					1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
					2.如果确定只是简单的展示数据,用index也是可以的。

setState

//对象式的setState
	this.setState({count:count+1},()=>{
		console.log(this.state.count);
			//第二个参数时回调
	})
//函数式的setState
	this.setState( state => ({count:state.count+1}))

	状态必须通过 setState 进行更新,且更新是一种合并,不是替换

配置代理

在项目根目录建立 setupProxy.js 文件 在文件中配置

const proxy = require("http-proxy-middleware");

module.exports = function (app) {
  app.use(
    proxy("/api1", {
      //遇见/api1 前缀的请求,就会触发该代理配置
      target: "http://localhost:5000", //请求转发给谁
      changeOrigin: true, //控制服务器收到的请求头中 Host 的值
      pathRewrite: { "^/api1": "" }, //重写请求路径(必须)
    }),
    proxy("/api2", {
      target: "http://localhost:5001",
      changeOrigin: true,
      pathRewrite: { "^/api2": "" },
    })
  );
};

发布订阅

安装 pubsub-js 包

import PubSub from 'pubsub-js'
//发布
PubSub.publish('atguigu',{isFirst:false,isLoading:true})
//订阅
componentDidMount(){
this.token = PubSub.subscribe('atguigu',(\_,stateObj)=>{
this.setState(stateObj)
})
}

//取消订阅
componentWillUnmount(){
PubSub.unsubscribe(this.token)
}

fetch

fetch 实例

  async search() {
    try {
      const response = await fetch(`/api1/search/users2?q=${keyWord}`);
      const data = await response.json();
      console.log(data);
    } catch (error) {
      console.log("请求出错", error);
    }
  }

路由

安装 react-router-dom@5.2.0

网络请求

	1.设计状态时要考虑全面,例如带有网络请求的组件,要考虑请求失败怎么办。
	2.ES6小知识点:解构赋值+重命名
				let obj = {a:{b:1}}
				const {a} = obj; //传统解构赋值
				const {a:{b}} = obj; //连续解构赋值
				const {a:{b:value}} = obj; //连续解构赋值+重命名
	3.消息订阅与发布机制
				1.先订阅,再发布(理解:有一种隔空对话的感觉)
				2.适用于任意组件间通信
				3.要在组件的componentWillUnmount中取消订阅
	4.fetch发送请求(关注分离的设计思想)
				try {
					const response= await fetch(`/api1/search/users2?q=${keyWord}`)
					const data = await response.json()
					console.log(data);
				} catch (error) {
					console.log('请求出错',error);
				}

路由的基本使用

		1.明确好界面中的导航区、展示区
		2.导航区的a标签改为Link标签
					<Link to="/xxxxx">Demo</Link>
		3.展示区写Route标签进行路径的匹配
					<Route path='/xxxx' component={Demo}/>
		4.<App>的最外侧包裹了一个<BrowserRouter><HashRouter>

路由组件与一般组件

		1.写法不同:
					一般组件:<Demo/>
					路由组件:<Route path="/demo" component={Demo}/>
		2.存放位置不同:
					一般组件:components
					路由组件:pages
		3.接收到的props不同:
					一般组件:写组件标签时传递了什么,就能收到什么
					路由组件:接收到三个固定的属性
						history:
									go: ƒ go(n)
									goBack: ƒ goBack()
									goForward: ƒ goForward()
									push: ƒ push(path, state)
									replace: ƒ replace(path, state)
						location:
									pathname: "/about"
									search: ""
									state: undefined
						match:
									params: {}
									path: "/about"
													url: "/about"

NavLink 与封装 NavLink

			1.NavLink可以实现路由链接的高亮,通过activeClassName指定样式名

Switch 的使用

			1.通常情况下,path和component是一一对应的关系。
			2.Switch可以提高路由匹配效率(单一匹配)。

解决多级路径刷新页面样式丢失的问题

			1.public/index.html 中 引入样式时不写 ./ 写 / (常用)
			2.public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)
			3.使用HashRouter

路由的严格匹配与模糊匹配

			1.默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
			2.开启严格匹配:<Route exact={true} path="/about" component={About}/>
			3.严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由

Redirect 的使用

			1.一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
			2.具体编码:
					<Switch>
						<Route path="/about" component={About}/>
						<Route path="/home" component={Home}/>
						<Redirect to="/about"/>
					</Switch>

嵌套路由

			1.注册子路由时要写上父路由的path2.路由的匹配是按照注册路由的顺序进行的

向路由组件传递参数

			1.params参数
						路由链接(携带参数):<Link to='/demo/test/tom/18'}>详情</Link>
						注册路由(声明接收):<Route path="/demo/test/:name/:age" component={Test}/>
						接收参数:this.props.match.params
			2.search参数
						路由链接(携带参数):<Link to='/demo/test?name=tom&age=18'}>详情</Link>
						注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
						接收参数:this.props.location.search
						备注:获取到的search是urlencoded编码字符串,需要借助querystring解析
			3.state参数
						路由链接(携带参数):<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>
						注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
						接收参数:this.props.location.state
						备注:刷新也可以保留住参数

路由懒加载

				import React, { Component,lazy,Suspense} from 'react'
				const Home = lazy(()=> import('./Home') )
				const About = lazy(()=> import('./About'))
							<Suspense fallback={<Loading/>}>
								<Route path="/about" component={About}/>
								<Route path="/home" component={Home}/>
							</Suspense>

编程式路由导航

借助 this.prosp.history 对象上的 API 对操作路由跳转、前进、后退 -this.prosp.history.push() -this.prosp.history.replace() -this.prosp.history.goBack() -this.prosp.history.goForward() -this.prosp.history.go() //replace 跳转+携带 params 参数 //this.props.history.replace(/home/message/detail/${id}/${title}) //replace 跳转+携带 search 参数 // this.props.history.replace(/home/message/detail?id=${id}&title=${title}) //replace 跳转+携带 state 参数 this.props.history.replace(/home/message/detail,{id,title}) //非路由组件使用 路由 import {withRouter} from 'react-router-dom' export default withRouter(Header) //withRouter 可以加工一般组件,让一般组件具备路由组件所特有的 API //withRouter 的返回值是一个新组件

BrowserRouter 与 HashRouter 的区别

		1.底层原理不一样:
					BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。
					HashRouter使用的是URL的哈希值。
		2.path表现形式不一样
					BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
					HashRouter的路径包含#,例如:localhost:3000/#/demo/test
		3.刷新后对路由state参数的影响
					(1).BrowserRouter没有任何影响,因为state保存在history对象中。
					(2).HashRouter刷新后会导致路由state参数的丢失!!!
		4.备注:HashRouter可以用于解决一些路径错误相关的问题。

antd 的按需引入+自定主题

		1.安装依赖:yarn add react-app-rewired customize-cra babel-plugin-import less less-loader
		2.修改package.json
				....
					"scripts": {
						"start": "react-app-rewired start",
						"build": "react-app-rewired build",
						"test": "react-app-rewired test",
						"eject": "react-scripts eject"
					},
				....
		3.根目录下创建config-overrides.js
				//配置具体的修改规则
				const { override, fixBabelImports,addLessLoader} = require('customize-cra');
				module.exports = override(
					fixBabelImports('import', {
						libraryName: 'antd',
						libraryDirectory: 'es',
						style: true,
					}),
					addLessLoader({
						lessOptions:{
							javascriptEnabled: true,
							modifyVars: { '@primary-color': 'green' },
						}
					}),
				);
			4.备注:不用在组件里亲自引入样式了,即:import 'antd/dist/antd.css'应该删掉

rudux

	安装包
		"react-redux": "^7.2.2",
		"react-scripts": "4.0.0",
		"redux": "^4.0.5",
		"redux-thunk": "^2.3.0",

		目录结构
		src
		-pages(存放用到router的组件)
		-components(存放不用redux的组件)
		-containers(存放用到redux的组件)
		-redux(redux相关文件)
		 	-store.js 总仓库
		  -constant.js 存放容易写错的TYPE类型 变量名用大写
			-actions 将传过来的数据包装成action对象,并且发送给reducer进行处理
				-count.js
				-person.js
			-reducers 更改数据,只能是纯函数
				-count.js
				-person.js
				-index.js 汇总后的reducer

redux 精简版

	(1).去除Count组件自身的状态
	(2).src下建立:
					-redux
						-store.js
						-count_reducer.js

	(3).store.js:
				1).引入redux中的createStore函数,创建一个store
				2).createStore调用时要传入一个为其服务的reducer
				3).记得暴露store对象

	(4).count_reducer.js:
				1).reducer的本质是一个函数,接收:preState,action,返回加工后的状态
				2).reducer有两个作用:初始化状态,加工状态
				3).reducer被第一次调用时,是store自动触发的,
								传递的preState是undefined,
								传递的action是:{type:'@@REDUX/INIT_a.2.b.4}

	(5).在index.js中监测store中状态的改变,一旦发生改变重新渲染<App/>
			备注:redux只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写。
			项目入口文件 `index.js` 加入 以下代码
			store.subscribe(()=>{
				ReactDOM.render(<App/>,document.getElementById('root'))
			})

redux 完整版

	新增文件:
		1.count_action.js 专门用于创建action对象
		2.constant.js 放置容易写错的type

redux 异步 action 版

	 (1).明确:延迟的动作不想交给组件自身,想交给action
	 (2).何时需要异步action:想要对状态进行操作,但是具体的数据靠异步任务返回。
	 (3).具体编码:
	 			1).yarn add redux-thunk,并配置在store中
	 			2).创建action的函数不再返回一般对象,而是一个函数,该函数中写异步任务。
	 			3).异步任务有结果后,分发一个同步的action去真正操作数据。
	 (4).备注:异步action不是必须要写的,完全可以自己等待异步任务的结果了再去分发同步action。

store写入以下代码

		//引入createStore,专门用于创建redux中最为核心的store对象
		import {createStore,applyMiddleware} from 'redux'
		//引入为Count组件服务的reducer
		import countReducer from './count_reducer'
		//引入redux-thunk,用于支持异步action
		import thunk from 'redux-thunk'
		//暴露store
		export default createStore(countReducer,applyMiddleware(thunk))

react-redux 基本使用

		(1).明确两个概念:
					1).UI组件:不能使用任何redux的api,只负责页面的呈现交互等
					2).容器组件:负责和redux通信,将结果交给UI组件
		(2).如何创建一个容器组件————靠react-redux 的 connect函数
						connect(mapStateToProps,mapDispatchToProps)(UI组件)
							-mapStateToProps:映射状态,返回值是一个对象
							-mapDispatchToProps:映射操作状态的方法,返回值是一个对象
		(3).备注1:容器组件中的store是靠props传进去的,而不是在容器组件中直接引入
		(4).备注2:mapDispatchToProps,也可以是一个对象

react-redux 优化

		(1).容器组件和UI组件整合一个文件
		(2).无需自己给容器组件传递store,给<App/>包裹一个<Provider store={store}>即可。
		(3).使用了react-redux后也不用再自己检测redux中状态的改变了,容器组件可以自动完成这个工作。
		(4).mapDispatchToProps也可以简单的写成一个对象
		(5).一个组件要和redux“打交道”要经过哪几步?
						(1).定义好UI组件---不暴露
						(2).引入connect生成一个容器组件,并暴露,写法如下:
								connect(
									state => ({key:value}), //映射状态
									{key:xxxxxAction} //映射操作状态的方法
								)(UI组件)
						(4).在UI组件中通过this.props.xxxxxxx读取和操作状态

react-redux 数据共享版

		(1).定义一个Pserson组件,和Count组件通过redux共享数据。
		(2).为Person组件编写:reducer、action,配置constant常量。
		(3).重点:Person的reducer和CountReducer要使用`combineReducers`进行合并,
				合并后的总状态是一个对象!!!
		(4).交给store的是总reducer,最后注意在组件中取出状态的时候,记得“取到位”。

		//引入combineReducers,用于汇总多个reducer
		import {combineReducers} from 'redux'
		//引入为Count组件服务的reducer
		import count from './count'
		//引入为Person组件服务的reducer
		import persons from './person'

		//汇总所有的reducer变为一个总的reducer
		export default  combineReducers({
			count,
			persons
		})

react-redux 开发者工具的使用

		(1).yarn add redux-devtools-extension
		(2).store中进行配置
				import {composeWithDevTools} from 'redux-devtools-extension'
				const store = createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))

react-redux 最终版

		(1).所有变量名字要规范,尽量触发对象的简写形式。
		(2).reducers文件夹中,编写index.js专门用于汇总并暴露所有的reducer

Context

const MyContext = React.createContext() const {Provider,Consumer} = MyContext

// 祖先组件 发送数据
			<Provider value={{username,age}}>
				<B/>
			</Provider>
// 字组件 接受数据
			<Consumer>
				{value => `${value.username},年龄是${value.age}`}
			</Consumer>

错误边界_ErrorBoundary

			子组件出问题,父组件里进行处理
import React, { Component } from "react";
import Child from "./Child";
export default class Parent extends Component {
  state = {
    hasError: "", //用于标识子组件是否产生错误
  };
  //当Parent的子组件出现报错时候,会触发getDerivedStateFromError调用,并携带错误信息
  static getDerivedStateFromError(error) {
    console.log("@@@", error);
    return { hasError: error };
  }
  componentDidCatch() {
    console.log("此处统计错误,反馈给服务器,用于通知编码人员进行bug的解决");
  }
  render() {
    return (
      <div>
        <h2>我是Parent组件</h2>
        {this.state.hasError ? <h2>当前网络不稳定,稍后再试</h2> : <Child />}
      </div>
    );
  }
}

react 插槽_renderProps

import React, { Component } from "react";
import "./index.css";

export default class Parent extends Component {
  render() {
    return (
      <div className="parent">
        <h3>我是Parent组件</h3>
        <A render={(name) => <B name={name} />} />
      </div>
    );
  }
}

class A extends Component {
  state = { name: "tom" };
  render() {
    console.log(this.props);
    const { name } = this.state;
    return (
      <div className="a">
        <h3>我是A组件</h3>
        {this.props.render(name)}
      </div>
    );
  }
}

class B extends Component {
  render() {
    console.log("B--render");
    return (
      <div className="b">
        <h3>我是B组件,{this.props.name}</h3>
      </div>
    );
  }
}