# React基础
TIP
什么是React
?
React
是一个用于构建用户界面的框架(采用的是MVC
模式):集中处理view
这一层- 核心专注于视图,目的实现组件化开发
什么是组件化开发? 我们可以很直观的将一个复杂的页面分割成若干个独立组件,每个组件包含自己的逻辑和样式 再将这些独立组件组合完成一个复杂的页面。 这样既减少了逻辑复杂度,又实现了代码的重用
- 可组合
- 可重用
- 可维护
# react
脚手架安装
安装
在全局下安装create-react-app
打开命令行工具输入以下代码
npm install create-react-app -g
默认会自动安装React,react-dom
两部分
# 使用 react
脚手架创建项目
TIP
需要在哪个文件夹下创建react
项目就在那个项目下打开命令行工具输入以下代码
create-react-app xxx # xxx为项目名称:不能出现大写字母
cd xxx # 转到项目目录下
yarn start 或 npm start # 启动项目即可
# react
和 react-dom
模块
TIP
在js
文件的顶部导入react
和react-dom
react
:核心模块(生命周期等钩子函数都是在这里定义的)react
中常用的方法:createElement
:在shouldComponentUpdate()
中实现时,使用了props
和state
的浅比较。Children
:我们可以用React.Children.map
去遍历this.props.children
获取到的结果Component
:继承需要用到的PureComponent
:纯函数
react-dom
:提供与DOM相关的功能react-dom
中常用的方法:render
:是React
的最基本方法,用于将模板转为HTML
语言,并插入指定的DOM
节点
import React from 'react';//核心基础 模块(生命周期)
//用来把react元素(jsx元素)渲染成真实的DOM结构并添加到页面当中
import ReactDOM,{render} from 'react-dom';
//ReactDOM中就一个方法比较常用叫render
//可以把render从react-dom中解构出来
# react
中的 jsx
语法与 render
渲染方法
# JSX
语法
语法
react
构建页面有自己的语法:JSX
语法;
jsx
是一种JS
和html
混合的语法,HTML
语言直接写在 JavaScript
语言之中,不加任何引号,这就是 jsx
的语法,它允许 HTML
与 JavaScript
的混写;jsx
最后会通过babel
进行转义
jsx
中的html
部分和原生html
基本一样(也有不一样的比如class
要用className
,label
的for
要用htmlFor
),并且属性名要使用驼峰命名法;
用jsx
语法写的内容叫做jsx
元素(虚拟DOM
元素);
jsx
元素具有不变性 :只在页面初次加载的时候渲染一次,以后只能通过更改状态和属性来重新渲染;
jsx
表达式的用法
- 1、可以放
JS
的执行结果:在react
中想将js
当作变量引入到jsx
中需要使用{}
- 2、在
jsx
中,相邻的两个jsx
元素 渲染时需要外面包裹一层元素(只能有一个根元素)
function build(str) {
return <h1>{str.name}</h1><h1>{str.name}</h1>;//在jsx语法中,这种写法是错误的
}
- 3、如果多个元素需要在
return
后面换行,我们需要加一个()
当作一个整体返回 - 4、可以把
JSX
元素当作函数的返回值 - 5、用
<
和{}
来区分是html
还是js
表达式:遇到HTML
标签(以<
开头),就用HTML
规则解析;遇到代码块(以{
开头),就用JavaScript
规则解析 - 6、循环绑定需要给元素设置唯一的
KEY
属性(属性值是唯一的),目的是为了方便React
做DOMdiff
;
jsx
属性的用法
在jsx
中分为普通属性和特殊属性
- 1、普通属性:和
html
中的一样 - 2、特殊的属性如
class
,要使用className
;for
(label
中的for
)要使用htmlFor
- 3、
style
要采用对象的方式
import React from 'react';
import ReactDOM,{render} from 'react-dom';
let str='<h1>纯标签</h1>';
let style={background:'red'};
render(<div>
<li className="aa"></li>
<li htmlFor="aa" style={style}></li>
<li dangerouslySetInnerHTML={{__html:str}}></li>
</div>,window.root);
- 4、
dangerouslyInnerHTML
插入html
(基本上用不到)
# React.createElement
React.createElement
用来创建一个react
对象,具有一个type
属性代表当前的节点类型,还有节点的属性props
;
React.createElement("div",null,"姜,",React.createElement("span",null,"帅哥"))
//结果如下
{type:'div',props:{className:"red",children:['姜',{type:'span',props:{id:'handsome',children:'帅哥'}}]}}
# render
TIP
此方法是由react-dom
模块中提供的方法
第一个参数为要渲染的jsx
元素
第二个参数为要把渲染的DOM
结构插入到哪个容器当中
第三个参数为渲染完成后需要执行的回调函数;
- 用来将虚拟
DOM
元素(jsx
元素)渲染成真实的DOM
;
ReactDOM.render(<div className='app'>姜<span id='handsome'>帅哥</span></div>,window.root);
# react
中的虚拟 DOM
我们在
react
中创建的组件和用jsx
语法写出的标签等,都是虚拟的DOM
元素:组件并不是真实的
DOM
节点,而是存在于内存之中的一种数据结构,叫做虚拟DOM
(virtual DOM
)。只有当它插入文档以后,才会变成真实的DOM
。根据React
的设计,所有的DOM
变动,都先在虚拟DOM
上发生,然后再将实际发生变动的部分,反映在真实DOM
上,这种算法叫做DOM diff
,它可以极大提高网页的性能表现。
# 虚拟 DOM
渲染过程(虚拟 DOM
—>真实 DOM
)
# 1、构建虚拟DOM元素(jsx元素):
TIP
- 在js中用jsx语法写的jsx元素就是虚拟DOM元素;(render中的元素)
render(<h1 className='red'>我是谁,我在哪</h1>,window.root)
# 2、调用render方法把虚拟DOM元素渲染成真实的DOM结构
TIP
1)
render
方法会先用babel
把jsx
元素转化为ES5
语法'h1', {className: 'red'}, '我是谁,我在哪'
2)再用
React.createElement
方法根据上面的结果创建出一个react
对象React.createElement('h1', {className: 'red'}, '我是谁,我在哪')
结果如下{type: "h1", props: {className: "red", children: "我是谁,我在哪"}}
3)此时
render
方法将react
对象转化为真正的DOM
结构,渲染到页面上render(<h1 className='red'>我是谁,我在哪</h1>,window.root)
自己模拟React.createElement
方法与render
方法:(render
不完善)
{type:'div',props:{className:"red",children:['姜',{type:'span',props:{id:'handsome',children:'帅哥'}}]}}
function createElement(type,props,...children) {
if(children.length === 1) children = children[0];
return {type,props:{...props,children:children}}
}
let myRender = (obj, container,callback) => {
let {type, props} = obj;
let ele = document.createElement(type);//创建第一层
for (let key in props) {
if (key === 'children') {
//children可能是数组也可能是一个字符串(纯文本)
if ({}.toString.call(props[key]).slice(8,-1)==='Array') {
props[key].forEach(item => {
if (typeof item === 'object') {//如果子元素是对象,那就继续调用render
myRender(item, ele);
} else {
ele.appendChild(document.createTextNode(item));
}
})
} else {//字符串的话直接创建文本节点并插入到元素中
ele.appendChild(document.createTextNode(props[key]));
}
} else if (key === 'className') {//className要做单独处理
ele.setAttribute('class', props[key]);
} else {
ele.setAttribute(key, props[key]);//其他的直接设置(style等属性未做处理)
}
}
container.appendChild(ele);//把DOM元素插入到html中
callback && callback();//=>添加成功后触发回调函数执行
};
# react
中的组件
react
元素是是组件组成的基本单位组件有两种声明方式
# 1、第一种方式是函数声明
函数声明
每一个创建的组件都是一个自定义的标签,在标签上设置的属性都会转换为react
对象的props
属性,当react
渲染DOM
的时候会把props
传递给下面例子中的Build
函数
import React from 'react';
import ReactDOM,{render} from 'react-dom';
let school1={name:'zfpx',age:10};
let school2={name:'珠峰',age:8};
let Build = function (props) {// "函数"(组件)的参数是属性
return <p>{props&&props.name}{props&&props.age}</p>;
};
render(<div>
<Build name={school1.name} age={school1.age}/>
<Build {...school2}/> {/* 利用解构赋值将对象中的内容解构出来传递给Build组件 */}
<h1></h1>
</div>,window.root);
# 2、第二种是以类的形式声明
类声明
import React,{Component} from 'react';
import ReactDOM from 'react-dom';
let school1 = {name:'珠峰',age:8};
let school2 = {name:'珠峰',age:0};
// 1.类声明的组件有this this上有props属性
class Build extends Component{//Component这个基类上有this.setState
render(){ // 这个组件在调用时默认会调用render方法(此render相当于注册组件)
let {name,age} = this.props;
return <p>{name} {age}</p>
// return <p>{this.props.name} {this.props.age}</p>
}
}
ReactDOM.render(
{/* 此render的渲染步骤
1)先渲染外层div,将jsx元素转化为ES5的语法,然后调用React.createElement方法创建react对象,当遇到div下的子元素的时候,会进行以下操作
//1、发现Build 是一个继承Component的类,它会创建这个类的一个实例,并且执行原型上的render方法
//2、执行原型上的render返回JSX元素,然后继续执行外层的render
2)继续将上面返回的jsx元素转化为es5语法,调用React.createElement方法创建虚拟DOM元素对象,Build 这个位置被返回的元素对象替换
3)外层ReactDOM.render将上面生成的react对象渲染成真实的DOM结构,并追加到页面中;*/}
<div>
<Build name={school1.name} age={school1.age}/>
<Build {...school2} />
</div>,window.root);
区别:类声明有状态,this
,和生命周期
声明组件的规则:
- 1、首字母必须大写,目的是为了和
JSX
元素进行区分 - 2、组件定义后可以像
JSX
元素一样进行使用 - 3、可以通过
render
方法将组件渲染成真实DOM
(做了优化 只会重新渲染改变的地方)
单闭合标签不能添加子节点;双闭合标签可以
# 组件中属性(props)和状态(state)的区别
区别
属性和状态的变化都会影响视图更新,但是两者导致视图刷新的机制是不一样的;
- 1、确切的说不是因为
props
更新了导致的视图刷新,而是传递属性的组件重新的渲染了,从而导致接受属性的组件也重新渲染,这样属性就也随着更新了; - 2、状态导致的视图刷新:只要通过
this.setState
改变状态,视图就会刷新;
# 组件的数据来源方式
# 1、通过props(属性) 获取数据:
TIP
外界传递进来的(存在默认属性和属性校验),是只读的,不能修改;
有属性校验模块:prop-types
(用来校验属性的类型和是否必须给)用yarn add prop-types
来安装。用来校验属性的
import React,{Component} from 'react';
import ReactDom,{render} from 'react-dom';
import PropTypes from 'prop-types';
// 1、默认的属性必须写在defaultProps中(不能改名字)
// 2、校验器的名字必须是propTypes(不能改名字);
class School extends Component{//类上的属性(把类当作对象添加的属性)就叫做静态属性
static defaultProps={//会先默认调用defaultProps
name:'珠峰',
age:1
};
static propTypes={//校验属性的值(number)类型和是否必须给(isRequired)
age:PropTypes.number.isRequired
};
constructor(props){
//如果想在constructor中使用props,则只能传递参数props,在constructor外面可以直接用this.props
//不能在组件中更改属性
super();
console.log(this.props)//undefined
}
render(){//渲染组件的,返回一个组件
//如果没有默认的defaultProps,也没有传递props,则this.props是一个空对象
return <h1>{this.props.name} {this.props.age}</h1>
}
}
//name={'我是谁'}:这样写大括号中就可以是变量
render(<School name={'我是谁'} age={9}/>,window.root);
# 2、this.state(状态) 状态是组件自己的
TIP
是可读写的(写了状态就必须给默认值);
我们可以通过this.setState({})
方法来修改状态,由于修改状态会引起视图刷新(即更新DOM
),这里面要经过React
核心中的diff
算法,最终才能决定是否进行重渲染,以及如何渲染。而且为了批次与效能的理由,多个setState
有可能在执行的过程中需要被合并,所以它被设计成异步来执行时相当合理的。
import React,{Component} from 'react';
import ReactDom,{render} from 'react-dom';
//1、state变化可以更新视图,只能通过this.setState({})来更改状态,调用后会更新视图
class Counter extends Component{
constructor(){
super();
console.log(this.state);//默认为undefined
this.state={count:0}
}
add = val => {
//setState方法会默认按最后的设置进行执行
// this.setState({count:this.state.count+val});
this.setState({count:this.state.count+val});//会执行这个
//setState有两种写法,一种是传入对象的方式,一种是传入函数的方式
//下一个状态是依赖于上一个状态时,需要写成函数的方式
this.setState(prevState=>({count:prevState.count+val}));//如果返回的就是一个对象,则需要用'()'包裹
//等同与下面这种写法
// this.setState({count:this.state.count+val},function () {
// this.setState({count:this.state.count+val});
// }
};
render(){
return <p>
{this.state.count}
<button onClick={this.add.bind(null,2)}>+</button>
</p>
}
}
render(<Counter/>,window.root);
# 复合组件与数据传递
复合组件:将多个组件进行组合嵌套;
复合组件之间的数据传递:
# 1、父传子
父组件传递给子组件数据通过设置属性的方式
import React,{Component} from 'react';
import ReactDom,{render} from 'react-dom';
import PropTypes from 'prop-types';
import 'bootstrap/dist/css/bootstrap.css';
//1、什么是复合组件,将多个组件进行组合
//2、结构非常复杂时可以把组件分离开
//3、复合组件 有父子关系,父的数据传递给子(通过给子组件设置属性)
// Panel组件 Heading Body
class Counter extends Component{
render(){
//由于在Counter中传递了属性,所以这里可以用this.props获取到;
let {header,body}=this.props;
return (
<div className={'container'}>
<div className='panel-default panel'>
{/* 这里写了之后就可以在下面的子组件中拿到对应的props值 */}
<Header header={header}></Header>
<Body body={body}></Body>
</div>
</div>
)
}
}
//react中需要把属性一层一层向下传递:单向数据流;
class Header extends Component{
render(){
return (
<div className='panel-heading'>
{/* 上面传递了之后就可以直接在此处使用this.props属性了 */}
{this.props.header};
</div>
)
}
}
class Body extends Component{
render(){
return (
<div className={'panel-body'}>
{this.props.body}
</div>
)
}
}
let data={header:'我很帅',body:'长的英俊'};
render(<Counter {...data}/>,window.root);
# 2、子传父
通过事件的方式:父亲传递给儿子一个函数,儿子调用父亲的函数将要改的值传递给父亲,父亲更新值,刷新视图;
import React, {Component} from 'react';
import ReactDom, {render} from 'react-dom';
import 'bootstrap/dist/css/bootstrap.css';
//1、子传父 通过父亲传递给儿子一个函数,儿子调用父亲的函数将要修改的值传递给父亲,父亲更新值,刷新视图;
class Counter extends Component {
constructor() {
super();
this.state = {color: 'primary'};
}
changeColor = (color) => {
this.setState({color});
};
render() {
let {header} = this.props;
return (
<div className='container'>
<div className={'panel panel-' + this.state.color}>
<Header header={header} color={this.changeColor}></Header>
</div>
</div>
)
}
}
//react中需要把属性一层一层向下传递:单向数据流;
class Header extends Component {
clickColor = () => {
this.props.color('danger');//儿子调用父亲的方法,把要改的值传递给父亲,让父亲自己修改
};
render() {
return (
<div className='panel-heading'>
{this.props.header}
<button className={`btn btn-danger`} onClick={this.clickColor}>改颜色</button>
</div>
)
}
}
let data = {header: '我很帅'};
render(<Counter {...data}/>, window.root);
# ref
属性
::: ref
ref
可以写在组件上也可以写在DOM
元素上,获取的时候得到两者的结果是不同的;
- 写在组件上时(这里的组件是有状态的组件),通过
refs
获取的是组件的实例; - 写在
DOM
元素上时,通过refs
获取的是具体的DOM
元素节点 :::
# ref的两种写法
# 1、直接写为一个值
通过
ref
设置的属性:可以通过this.refs.xxx
获取到对应ref
值为xxx
的dom
元素或者组件的实例
# 2、写成一个函数的形式
写成函数时,这个函数将会在渲染成真实
DOM
结构或组件被挂载后执行,函数的参数为真实的DOM
结构或组件的实例
//如上面非受控组件中的例子
//1、直接将ref写为一个值
<input type="text" ref='a'/>
//2、将ref写为一个函数
<input type="text" ref={(x)=>this.c=x}/>
{/* x 代表真实的DOM(写成函数的形式会默认传入当前设置ref的DOM结构 ),上面这种写法相当于把DOM结构挂载到了this的自定义属性c上,以后可以直接用this.c拿到这个input的DOM结构*/}
resultChange=()=>{//通过ref设置的属性:可以通过this.refs.xxx获取到对应ref值为xxx的dom元素
this.c.value=parseFloat(this.refs.a.value)+parseFloat(this.refs.b.value);
};
# 受控组件与非受控组件
当引入
HTML
表单元素时,根据是否将数据托管到React
组件中还是将其依然保留在DOM
元素中来区分为受控与非受控组件,受控组件可以赋予默认的值
# 受控组件
指交由
React
控制并且所有的表单数据统一存放的组件,需要与onInput/onChange/disabed/readOnly
等控制value/checked
的属性或事件一起使用
import React, {Component} from 'react';
import ReactDom, {render} from 'react-dom';
class Input extends Component {
//1、受状态控制的组件,必须要有onChange方法,否则不能使用;
//2、受控组件可以赋予默认值(官方推荐使用受控组件)
constructor() {
super();
this.state = {val: '', a: 1, b: 2};//初始化状态:设置默认值
}
inputChange = (val, e) => {//处理多个输入框的值映射到this.state的方法
//val:要修改的state中的属性的名称
//e:事件源(e.target.value是要修改成的值)
this.setState({[val]: parseFloat(e.target.value)||0});//更改状态
};
render() {
return (
<div>
{/* 下面两个input都受this.state影响(受控组件) */}
<input type="text" value={this.state.a} onChange={this.inputChange.bind(null, 'a')}/>
<input type="text" value={this.state.b} onChange={e =>{
// 方法执行的时候要用到事件对象e,所以必须要传
this.inputChange('b', e)
}}/>
{this.state.a + this.state.b}
</div>
)
}
}
render(<Input/>, window.root);
# 非受控组件(基于 ref
来管理)
而非受控组件则是由
DOM
存放表单数据;我们可以使用refs
来操控DOM
元素
import React,{Component} from 'react';
import ReactDom,{render} from 'react-dom';
class Input extends Component{
//输入框value值不受状态控制,不能初始化默认值
resultChange=()=>{//通过ref设置的属性:可以通过this.refs.xxx获取到对应ref值为xxx的dom元素
this.c.value=parseFloat(this.refs.a.value)+parseFloat(this.refs.b.value);
};
render(){
return (
<div onChange={this.resultChange}>
<input type="text" ref='a'/>
<input type="text" ref='b'/>
{/*x代表真实的DOM(写成函数的形式会默认传入当前设置ref的DOM结构),下面这种写法相当于把元素挂载到了this的自定义属性c上*/}
<input type="text" ref={(x)=>this.c=x}/>
</div>
)
}
}
render(<Input/>,document.getElementById('root'));
# 受控组件与非受控组件的区别
虽然非受控组件看上去更好实现,可以直接从
DOM
中获取数据,而不需要额外的代码。不过实际开发中我们不建议使用非受控组件,因为更多情况下我们需要考虑到表单验证、选择性的开启或者关闭点击按钮、强制输入格式等功能的支持,而此时我们将数据托管到React
中有助于我们更好的以声明式的方式完成这些功能;引入React
的原因就是为了将我们从繁重的直接操作DOM
中释放出来
# react
元素中的事件绑定
事件
给react
元素(jsx
元素)事件绑定对应的方法时,方法如果不做处理,那么执行的时候方法中的this
默认是undefined
可通过如下方式解决:
- 1、使用
bind
方式;(绑定的时候处理) - 2、使用
ES6
的箭头函数(绑定时候处理:在外层包一层箭头函数) - 3、使用
ES7
的箭头函数(在类的原型上定义的时候处理:写为:函数名=()=>{})
# React
中的生命周期
生命周期
在react
操作过程中,设定了很多的执行阶段,每个阶段提供了不同方法
[初期渲染]
defaultProps
:默认属性设置与校验
constructor
:获取属性和设置默认初始状态
componentWillMount(只执行一次)
:constructor
执行完成后,DOM
结构开始加载之前,执行此方法,
render(开始渲染)
componentDidMount(只执行一次)
:DOM
结构加载完成后会自动触发此函数
[状态发生改变时触发]
shouldComponentUpdate
:状态更新时会触发此方法,返回布尔值
componentWillUpdate
:如果shouldComponentUpdate
返回true
则执行此函数,否则不执行
render(开始渲染)
componentDidUpdate
:组件完成更新后触发此方法
[卸载组件时触发]
ReactDOM.unmountComponentAtNode
:删除某个组件
componentWillUnmount(组件将要卸载)
:组件移除时会调用此方法,一般在这个方法中我们要清除定时器
[属性发生改变时触发]
componentWillReceiveProps
import React, {Component, PureComponent} from 'react';
import ReactDom, {render} from 'react-dom';
class Counter extends Component {/*PureComponent是纯组件:比较的是状态的地址,如果是同一个地址,则不会更新,所以状态最好采用新的状态替换掉老的*/
static defaultProps = {
name: 'zfpx'
};
constructor(props) {
super();
this.state = {number: 0};
console.log('1、constructor');
}
componentWillMount() {//同步代码放在此处最好:如获取本地的数据:在渲染之前获取数据,只渲染一次
console.log('2、父组件将要加载');
}
componentDidMount() {
this.setState({number: this.state.number + 1});
console.log('5、父组件已经挂载完成');
}
click = (e) => {
this.setState({number: this.state.number + 1});
};
//react可以在shouldComponentUpdate方法中优化 PureComponent 可以帮我们做这件事
shouldComponentUpdate(nextProps, nextState) {//分别代表下一次的属性和下一次的状态
//组件是否需要更新
console.log('父组件是否需要更新');
return nextState.number % 2;//如果此函数返回了false,就不会调用render方法了
}
//乱用this.setState({})会造成栈溢出
componentWillUpdate() {
console.log('父组件将要更新');
}
componentDidUpdate() {
console.log('父组件完成更新');
}
render() {
console.log(`3、父组件render(渲染)`);
return (
<div>
<p>{this.state.number}</p>
<ChildCounter n={this.state.number}/>
<button onClick={this.click}>+</button>
</div>
)
}
}
class ChildCounter extends Component {
componentWillReceiveProps(newProps) {//第一次不会执行,之后属性更新时才会执行
console.log('子有新的属性');
}
render() {
console.log('4、child render');
return (
<div>
{this.props.n}
</div>
)
}
shouldComponentUpdate(nextProps, nextState) {
console.log('子组件是否需要更新');
return nextProps.n % 3;
}
}
render(<Counter name={'计数器'}/>, window.root);
注意:
最好不要在任何一个状态更新时触发的方法中写this.setState()
,如果不加条件控制的话,会再次触发状态更新而导致方法执行,这样就会造成栈溢出;
/*页面初始化时触发*/
defaultProps
constructor
componentWillMount //(将要挂载)页面初次加载时执行1次
render
componentDidMount // (完成挂载)页面初次加载完成时执行1次
/*状态更新时会触发*/
shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate
/*属性更新*/
componentWillReceiveProps
/*卸载*/
componentWillUnmount
# 获取组件的所有子节点
获取子节点
在函数式声明的组件中我们用props.children
来获取组件的所有子节点;
在以类声明的组件中我们用this.props.children
来获取组件的所有子节点;
render(<Dinner>
<div>吃啥</div><div>吃啥</div>{ /* 此处相当于组件的子节点 */ }
</Dinner>,window.root);
this.props.children
的值有三种情况:
- 1、如果组件中间没有子节点,值是
undefined
; - 2、如果有一个子节点,获取的是对象数据类型的值
- 3、如果有多个子节点,获取的是数组类型的值
我们可以用React.Children.map
去遍历this.props.children
获取到的结果
import React from 'react';
import ReactDom,{render} from 'react-dom';
//this.props.children是获取组件中间的内容
//有三种可能的值:
// 1、不传的话默认是undefined;
// 2、传一个时是对象类型
// 3、传多个是数组类型
//我们可以用React.Children.map去遍历this.props.children
export default class Dinner extends React.Component {
render(){
return (
<div>
{React.Children.map(this.props.children,(item,index)=>(
<li>{item}</li>
))}
</div>
)
}
}
render(<Dinner>
<div>吃啥</div><div>吃啥</div>{/*此处相当于组件中间的内容*/}
</Dinner>,window.root);
React路由 →