跳至主要內容

基础语法

xiaoye大约 17 分钟ReactReact基础语法

基础语法

jsx/tsx【这个很重要】

全称: javascript and xml(html), 作用就是将js与html结合再一个文件中

描述: 对js进行了扩展,让js文件可以直接定义节点类型,而且使用节点给变量赋值!理解为一个新的模板语法(可以向模板中插入值)类比成vue中template

只有一个根节点

<React.Fragment>
    <div className='A'>

     </div>
	<div className='B'>1123</div>
</React.Fragment>
<>
    <div className='A'>

     </div>
	<div className='B'>1123</div>
</>

占位符({})在节点中间

/**
         * {} 出现在节点中
         *  - 变量和值
         *      - 字符串 数字可以渲染 对于布尔、null、undefined页面不会渲染
         *      - 对象:一般的对象直接报错, 有特殊情况(虚拟dom)
         *      - 数组: react首先会对数组的每一项进行校验, 校验通过,把每一项拿出来,渲染到页面上
         *  - 表达式: if for while 不是表达式是流程控制语句
         *      - 数学运算
         *      - 三目   (条件判断) 类比vue的v-if
         *      - 数组的操作方法 (列表循环)  类比与vue中的v-for
         *  - 函数执行的结果
         *      - 要注意函数执行的结果返回的值要满足上诉要求
         */

占位符在节点上

 /**
         * {} 出现在节点上(动态属性,类似于vue中的v-bind)
         *  - 字符串 
         *  - 条件判断
         *  - 函数(绑定事件·),函数执行结果
         *  - 注意:给节点设置类名要用:className   给节点设置行内样式要用:style(style一定是一个对象!)
         */
render() {
        const model = [1,2,3,4,'我是字符串', <span>124</span>, <p>我是p标签</p>]
        const format = () => 'format'
        // flag为true的时候 给一个红色的类名:red  flag为false的时候给一个绿色的类名:green
        // style行内样式来控制
        const flag = false

        const sayHi = () => {
            alert('hello')
        }


        return (
            <div>
                {/* <h1 className={ flag ? 'red':'green' } id={flag ? 'active' : ''}>{ format() }</h1> */}
                {/*  */}
                <h1 style={{ color: flag ? 'red' : 'green' }} id={flag ? 'active' : ''}>{ format() }</h1>
                <button onClick={ sayHi }>点击我</button>
            </div>
        )
    }

jsx属性与html的不同

不同点jsxhtml
类名的设置classNameclass
行内样式style跟上对象style跟上字符串
绑定事件onClick=函数onclick="函数名字"

classnames的使用

classnames帮你快速生成节点的类名,类似于vue中的class(class可以是个字符串,也可以是个表达式,页可以是个对象)用法。

// 有一个p标签,它有两个固定的类名: title  box
// 然后有一个类名: red 是根据变量isRed的变量来判断
<p class="title box" :class="{red: isRed}">reddd</p>

以上是vue的实现方法,把静态的类名与动态的类名分开了

react本身不能做到上诉功能,但是可以借助第三方包:classnames(就是一个函数)来完成

  • 安装:yarn add classnames

    import classnames from 'classnames'
    
    <p className={classnames('title', 'box', {red: isRed})}>reddd</p>
    

    实现了将静态类名与动态类名分开的效果

指令的实现

react本身是没有像vue的指令(v-if v-for v-show),但是可以自己实现。

列表循环(v-for)

render() {
    const arr = ['xfd', 're3r4', 'zssdf', '2323']
    return (
    	arr.map(item => <p key={item}>{item}</p>)
    )
}

条件判断(v-if, v-show)

<h1 style={{ display: isShow ? '' : 'none' }}>v-show</h1> 
{
    isIF ? <h1>v-if</h1> : null
} 

jsx渲染流程


/**
 * 第一次渲染流程
 * 1. jsx表达式变成一个render函数(babel:js兼容ie,代码转换(es6--》 es5))
 * 2. render函数会执行,执行完了之后返回一个虚拟dom对象(普通的js对象,回来描述真实dom节点)
 * 3. 拿到这个虚拟的dom节点  ----》 变成真实的dom节点,然后挂载到响应的dom节点上
 */

/**
 * 更新流程
 * 1.组件的状态发生变化, 重新生成虚拟dom对象 (新的虚拟dom对象)
 * 2.新的虚拟dom对象 与 上一次(老的)虚拟dom对象进行对比(dom-diff), 得到一个补丁包对象
 * 3.拿到补丁包之后,局部的去更新
 */`

class语法回顾

// class是一个es6出来的语法糖,用来快速的定义一个类
// Person就是一个函数(对象)
class Person {
    /**
     * 给人类定义属性
     * - 直接定义
     * - 有构造函数(constructor)
     */
    // 直接定义属性(name)
    name = '张三'
    // 构造函数的方式, new的时候会执行次函数
    constructor(name) {
        // this是执行我们创造的那个实例
        this.name = name
    }

    // eat方法在原型上,所有实例共享 (普通的函数)
    // eat是一个普通函数:this非常灵活
    eat() {
        console.log(this)
        console.log(`${this.name}在吃饭!`)
    }
    // run方法,定义为箭头函数
    // run写成箭头函数,不会放在原型上,每一个实例都有一个run方法
    // 好处就是:this指向实例(待会我们遇到了this问题)
    run = () => {
        console.log(`${this.name}在跑步!`)
    }

    // 静态方法或者属性, 访问, 给Person这个函数对象添加了一个count属性: Person.count
    static count = 10
}

// 继承

class Man extends Person{
    sex = '男'
    // 子类继承了父类,如果在子类中写了constructor,一定要写执行super
    // 子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。
    constructor(name) {
        // 调用过一次父类的构造函数  Person.constructor.call(this, name):call继承(用来继承属性的!)
        super(name)
    }
}

const m = new Man('男人1')
console.log(m)

类类型组件底层渲染逻辑

定义类组件

  • 如果类组件写了constructor
    • constructor必须要有props参数(props接收实例化的时候传递的值!)
    • 必须要在constructor里面执行super
  • 默认constructor可以不写!
export default class App extends Component<any, any> {

  constructor(props) {
    super(props)
    console.log('App 被实例化了!!!')
  }

  render() {
    console.log(this)
    return (
      <div className='jsx-wrapper'>
        <h1>111</h1>
        <h1>222</h1>
        {React.createElement("h1", null, "7787878")}
      </div>
    )
  }
}

实例化类组件

  • 初始化props
// { title: 'title', name: 'zzz', age: 24,  children: 虚拟dom对象或者虚拟dom对象数组 }
<App title=“title” name='zzz' age={24}>
    // 把span(jsx) --》 变成虚拟dom对象
	<span>我在app下</span>
</App>
  • new App(props) --> 此处的props就是第一步拼装好的对象
  • 实例化之后得到一个实例,调用实例上的方法:render。 进行页面渲染
  • render调用之后就是jsx渲染流程(jsx--》 渲染的代码块--》虚拟dom--》真实dom)

组件的状态

如果你要做到数据变化,页面更新。请把这类数据放在组件的state下,修改请求使用setState进行修改

// 定义
state = {num1: 'xx', age: 24, email: 'xxxx'}
agree = () => {
    // 修改
    this.setState({
      num1: this.state.num1 + 1
    })

  }

<div>赞成:{ num1 }<button onClick={ this.agree }>赞成+1</button></div>

注意:初始使用setState可以改变数据更新页面,我们页可以强制的更新页面(forceUpdate),这玩意一定要慎用

事件绑定与传参【这个重要得很哟】

箭头函数+bind传参【主要解决方式】

// 定义成箭头函数,你就不用管this指向问题,它会永远指向实例
agree = (num) => {
    this.setState({
      num1: this.state.num1 + num
    })
 }

render() {
    return (
    	<div>
        	<button onClick={this.agree.bind(this, 4)}>点击我</button>
        </div>
    )
}

记住上诉方式,类组件里面绑定事件与传参就ok了

闭包的方式也可以传参【了解一哈,毕竟闭包闭多了脑壳疼】

当然很多人习惯于vue的那种传参方式:

  • onClick就是要一个函数,agree执行之后,返回一个函数
// 定义成箭头函数,你就不用管this指向问题,它会永远指向实例
agree = (num) => {
    // 外层函数就是一个保留num参数的功能
    return () => {
        // 内层函数执行真正的逻辑
    	this.setState({
	      num1: this.state.num1 + num
    	})    
    }
    
 }

render() {
    return (
    	<div>
        	<button onClick={this.agree(4)}>点击我</button>
        </div>
    )
}

通过自定义属性+事件对象传参【了解】

unAgree = (e) => {
    // 去button上的一个自定义属性 data-num
    console.log(e.target.dataset.num)
 }


<div>反对:{ num2 }<button data-num={5} onClick={this.unAgree}>反对+1</button></div>

父子组件通信

父传子(通过props)

  • 在父组件里实例化子组件的时候,拼装props

子传父

  • 父组件的函数给子组件执行
  • 要传递的数据,做为此函数的实参
import React, { Component } from 'react'
import Child from './Child'

export default class App extends Component {
    state = {
        name: '小张三'
    }
	// 父组件的函数
    getMoney = (money) => {
        debugger
        console.log(`我是爸爸,我得到了儿子的:${money}`)
    }

    render() {
        return (
            <div>
                <h1>我的儿子组件:</h1>
                <Child getMoney={this.getMoney} name={this.state.name}/>
            </div>
        )
    }
}

父组件

import React, { Component } from 'react'

export default class Child extends Component<any, any> {

    constructor(props) {
        super(props)
        console.log(props)
    }  

    sendMoney = () => {
        debugger
        let data = '1w'
        // this.$emit('自定义事件名', data)
        //传data给父组件
        this.props.getMoney(data)
    }

    render() {
        return (
            <div>
                我是儿子,我叫:{this.props.name}
                <button onClick={this.sendMoney}>孝敬爸爸</button>
            </div>
        )
    }
}

子组件

react来实现具名插槽

import React, { Component } from 'react'
import './Layout.css'

export default class Layout extends Component<any, any> {

    constructor(props) {
        super(props)
        console.log(props)
    }

    render() {
        const {children} = this.props
        return (
            <div className='layout-wrapper'>
                <div className='left'>
                    {/* <slot name="left"></slot> */}
                    { children.find(item => item.props.slot === 'left') }
                </div>
                <div className='right'>
                    <div className='right-top'>
                        {/* <slot name="righttop"></slot> */}
                        { children.find(item => item.props.slot === 'righttop') }
                    </div>
                    <div className='right-bottom'>
                        {/* <slot name="rightbottom"></slot> */}
                        { children.find(item => item.props.slot === 'rightbottom') }
                    </div>
                </div>
            </div>
        )
    }
}
<Layout>
    {/* h1 最终会变为一个虚拟dom对象, slot属性肯定共有一个地方放着 */}
    <h1 slot="righttop">右上</h1>
    <h1 slot="rightbottom">右下</h1>
    <h1 slot="left">左边</h1>
    <h1 slot="left">左边1</h1>
    <h1 slot="left">左边2</h1>
</Layout>

类类型组件props进行校验

jsx版本

官方例子:https://zh-hans.reactjs.org/docs/typechecking-with-proptypes.htmlopen in new window

  • 安装prop-types
  • 引入prop-types
  • 类.propTypes进行校验
import PropTypes from 'prop-types';

export default class Test extends Component {
  render() {
    return (
      <div>Test</div>
    )
  }
}

Test.propTypes = {
    // props下有一个name属性,这个属性是string类型,而且这个属性必须有值
    name: PropTypes.string.isRequired,
    // sex是string或者number
    sex: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.number
    ]).isRequired,
    // // 定义对象
    obj: PropTypes.shape({
        name: PropTypes.string
    })
}

// 设置默认值
Test.defaultProps = {
    name: 'default',
    sex: 'zzz',
    obj: {name: 'yyy'}
}

propTypes 与 defaultProps可以写成静态属性的方式与上诉方式是相同

export default class Test extends Component {

  static propTypes = {
    // props下有一个name属性,这个属性是string类型,而且这个属性必须有值
    name: PropTypes.string.isRequired,
    // sex(男女这种字符串, 0,1这种数字), sex是string或者number
    sex: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.number
    ]).isRequired,
    // // 定义对象
    obj: PropTypes.shape({
      name: PropTypes.string
    })
  }

  static defaultProps = {
    name: 'default',
    sex: 'zzz',
    obj: { name: 'yyy' }
  }

  render() {
    console.log(this.props)
    return (
      <div>Test</div>
    )
  }
}

tsx版本

没有儿子的版本

import React, { Component } from 'react'
import classnames from 'classnames'
import './Tabs.css'

type tabprops = {
  active: number
  list: {text: string, val: number}[]
  onChange?: (val:number) => void 
}

export default class Tabs extends Component<tabprops, any> {
  render() {
    const { list, active } = this.props
    console.log(this.props)
    return (
      <ul>
        {list.map(item => <li onClick={() => this.props.onChange(item.val)}  className={classnames({active: item.val === active})} key={item.val}>
          {item.text}
        </li>)}
      </ul>
    )
  }
}

当这个组件有儿子的时候,props下一定会有一个属性(children),这个属性不需要你定义,请借助react提供的一个类型(PropsWithChildren)进行定义

type layouttype = {
    name?: string,
}

export default class Layout extends Component<PropsWithChildren<layouttype>, any> {
}

深入组件渲染流程-生命周期

单个组件的生命周期

  • 挂载
    • 整个过程:类组件实例化(初始化props),调用render方法,把jsx变成真实的dom
    • 初始化props,然后执行new App(props) (), 进入App的constructor
    • 初始化之后得到实例,实例.render(), render
    • rende执行,jsx变成真实的dom节点,渲染到页面上。
    • 第一次页面渲染完成,进入生命周期:componentDidMount。该生命周期作用如下:
      • 操作dom节点:初始化echarts, 初始化swiper
      • 发起请求通常在这里
      • 订阅: 订阅定时器, 订阅一些事件(监听window的resize事件)
  • 更新
    • 触发更新的条件: state props forceUpdate
    • shouldComponentUpdate(nextProps,nextState)
      • nextState,nextProps 即将更新的state或者props
      • 返回值: true, false
    • render
      • 渲染页面
    • componentDidUpdate(prevProps, prevState): 更新完成!
      • prevProps, prevState 上一次的props和state
  • 卸载
    • componentWillUnmount
      • 取消订阅,清除定时器~~~~

父子组件的生命周期执行顺序

  • 挂载阶段
    • 父组件的构造函数 --》 父组件的render --》子组件的构造函数 --》子组件的render --》 子组件的componentDidMount--》 父组件的componentDidMount
  • 更新阶段
    • 父组件的shouldComponentUpdate --> 父组件render --》 子组件shouldComponentUpdate --》 子组件的render --》 子组件 componentDidUpdate --> 父组件componentDidUpdate
  • 卸载阶段
    • 父组件componentWillUnmount --》 子组件 componentWillUnmount

获取dom节点 【这个要会哟】

你可以用原生的方式【不推荐哈】

ref的方式

  • refs的方式 ---》 类比vue2的方式 【废弃】

  • ref写成函数的方式 【主流】

  • createRef创建一个ref变量的方式 --》类比vue3的方式 【掌握】

    export default class Life extends Component {
        
        h2 = null
    	// 这是ts设置类型的方式【了解】
    	// h3Ref = createRef<HTMLHeadingElement>()
    	h3 = createRef()
        componentDidMount() {
            // 1. refs的方式 -- 跟vue2一模一样  [废弃]
            this.refs.h1ref
            // 2. ref写成函数 
            this.h2
            // 3. 借助createRef方法的方式
            this.h3.current
        }
        
        render() {
            console.log('render~~~')
            return (
                <div id='life'>
                	{ /* 这个节点加载完成之后,会往实例的refs属性里面添加:{ h1ref: h1这个节点}  */ }
                    <h1 ref="h1ref">111</h1>
                    {/* 形参的ref就是h2 */}
                    <h2 ref={ ref => { this.h2 = ref } }>222</h2>
                    <h3 ref={this.h3Ref}>333</h3>
                </div>
            )
        }
    }
    

受控(非受控)表单(v-model的实现)

  • 将组件的状态保存在state里

  • 将状态与表单绑定在一起

  • 表单输入发生变化重新给状态设置值

    export default class Input extends Component {
    
        state = {
            acc: 'test',
            pwd: 'test'
        }
    
        // 拿到事件对象,拿到事件对象.target  再拿input的value
        accInput = (e) => {
            this.setState({
                acc: e.target.value
            })
        }
    
        pwdInput = () => {
    
        }
    
        submit = () => {
            console.log(this.state)
        }
    
    
    
        render() {
            return (
                <>
                    <div>
                        账号<input onInput={this.accInput} value={this.state.acc} type="text"></input>
                    </div>
                    <div>
                        密码<input onInput={e => this.setState({pwd: (e.target as HTMLInputElement ).value})} value={this.state.pwd} type="text"></input>
                    </div>
                    <div>
                        <button onClick={this.submit}>提交</button>
                    </div>
                </>
            )
        }
    }
    

深入setState

  • setState是异步的

    state = {
        x: 0,
        y: 5,
        z: 10
    }
    add = () => {
        const {x, y, z} = this.state
        // react有一个更新队列(数组),接收setState的操作。 最后做一次批处理,然后再更新页面
        // 1. 在react18 this.setState操作是异步的
        this.setState({
            x: x + 1
        })
        // 此时x并没有加1,还是0
        console.log(this.state.x)
    }
    
  • setState第二个参数为回调,可以实现Vue的nexttick的效果

    • 次状态修改完成,而且重新执行render,页面渲染成功,进入componentDidUpdate, 然后进入此回调
    • 就算shouldComponentUpdate返回false,还是会进入此回调
    add = () => {
            const {x, y, z} = this.state
            // react有一个更新队列(数组),接收setState的操作。 最后做一次批处理,然后再更新页面
            // 1. 在react18 this.setState操作是异步的
            // 2. setState有第二个参数,是一个函数。函数什么时候执行了,次状态修改之后,页面更新完成,之后才执行
            this.setState({
                x: x + 1
            }, () => {
                // 修改了此状态,页面更新完毕之后,执行次回调!!!  ---》 vue的nexttick  【重点】
                // 就算shouldComponentUpdate返回的也是false,这个回调函数也会执行 【了解】
                console.log(this.state.x)
            })
        }
    

函数组件

  • 就是一个函数: 函数返回一段jsx
  • 优点: 语法简单, 函数组件写起来没有this,性能更好
  • 局限: 没有自己的状态, 没有自己的生命周期
  • 场景: 如果我们的组件不需要生命周期、不需要有自己的状态,就可以用他.例如:tabs组件就非常适合用函数组件
export default function ChildFun(props) {
    const { active, list,  onChange} = props
    return (
        <ul>
            {
                list.map(item => <li onClick={() => onChange(item.value)} className={classNames({active: item.value === active})} key={item.value}>{item.label}</li>)
            }
        </ul>
    )
}

以上是重构tab是的案例,更加简单

props校验

  • js版本的校验与类类型一直都是借助第三方的包: prop-types

    // 函数类型就是对象
    ChildFun.propTypes 
    
    ChildFun.defaultProps
    
  • ts版本

    • 校验props,就是校验一个函数的参数的类型。直接使用:

    • ts下设置默认值,还是使用defaultProps

      type tabitem = {
          label: string
          value: number
      }
      type tabsprops = {
          active: number,
          list: tabitem[],
          onChange?: (val:number) => void
      }
      
      export default function ChildFun(props:tabsprops) {
          const { active, list, onChange} = props
          return (
              <ul></ul>
          )
      }
      
      // 设置了一个默认props变量,并且使用tabsprops类型进行约束
      const defaultProps:tabsprops = {
          list: [
              { label: '全部111', value: 0 },
              { label: '男', value: 1 },
              { label: '女', value: 2 },
          ],
          active: 0,
          onChange: f => {}
      }
      
      // 然后把这个约束过后的变量赋值给ChildFun.defaultProps
      ChildFun.defaultProps = defaultProps
      

hook

什么是react的hook

是一个一个以use开头的函数,是react16.8之后的特性。主要是让函数组件有状态或者其他的react特性(生命周期

useState的使用

  • 让函数组件有状态

  • 基本用法: const [ count, setCount ] = useState(0)

  • 参数

    • 状态的初始值
  • 返回值

    • 类型:数组,长度为2
    • 第一个是:状态
    • 第二个是: 修改此状态的专有函数

sueState与ts结合

  • 设置状态的类型
type usertype = {
    name: string
    age: number
}

const [count, setCount] = useState<number>(0)

const [user, setUser] = useState<usertype>({name: '张三', age: 24})

useEffect

useEffect就是让函数组件拥有生命周期,你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。 学习得过程中,你可以把useEffect类比这vue3的watchEffect来学

语法:

  • 参数1: 回调函数, 参数2: 可以不传,可以传空数组, 也可以传具体的数组: [状态, props]
  • useEffect的回调都会在页面加载完之后,执行一次
function useEffect(effect: EffectCallback, deps?: DependencyList): void;

componentDidMount

useEffect(() => {
    
}, [])

第二个参数传空数组,代表所有状态都不监听。 页面加载完会默认执行一次,以后都不会再执行。

componentDidUpdate

useEffect(() => {
    
})

第二个参数不传,代表监听所有。所有组件的任何一个state发生变化,props发生变化,都会执行。与componentDidUpdate 唯一区别就是一开始会默认执行一次

useEffect(() => {
    
}, [状态1, 状态2])

监听状态1和状态2, 然后两者只要其中一个发生变化,都会进入它的回调.

componentWillUnmount

useEffect的回调函数,然后一个函数。组件卸载的时候就会执行返回的那个函数

useEffect(() => {
    return () => {}
}, [])

自定义hook

与vue3.2一样的思路,就是将一部分功能导出去,然后使用函数进行包裹。然后再引入使用

import { useState } from 'react'
export function useLogin() {
    const [acc, setAcc] = useState('xxx')
    const [pwd, setPwd] = useState('xxx')
    const submit = () => {
        console.log({acc, pwd})
    }
    return { acc, setAcc, pwd, setPwd, submit }
}

自定义hook

import { useLogin } from './hook/useUser'

const { acc, setAcc, pwd, setPwd, submit } = useLogin()

使用

react-router-dom(v6)

使用步骤

  • 安装 yarn add react-router-dom

一级路由的划分

  • 路由容器

    import React from 'react'
    import ReactDOM from 'react-dom/client'
    import App from './App'
    import { HashRouter } from 'react-router-dom'
    
    ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
        <HashRouter>
            <App />
        </HashRouter>
    )
    
    

    HashRouter为hash模式的路由容器,所有跟路由相关的组件需要放在其下面,所以建议直接在main.tsx中包裹App

  • Routes

    • 包裹Route,根据地址决定渲染哪个组件
  • Route

    • 将地址与react组件一一对应起来
  • 404配置方式

    <Route path='/*' element={<h1>404</h1>}></Route>
    
  • 重定向

    <Route path='/' element={<Navigate to={'/login'}></Navigate>}></Route>
    

    借助Navigate来实现重定向

import React, { Component } from 'react'
import { HashRouter, Route, Routes,Link, Navigate } from 'react-router-dom'
import Login from './views/Login'
import Home from './views/Home'

export default class App extends Component {
  render() {
    return (
      <>
        {/* 需要一个路由的容器:HashRouter 表示hash模式 */}
          <Link to={'/home'}>首页</Link> <Link to={'/login'}>登录</Link>
          {/* 根据地址决定渲染哪一个Route */}
          <Routes>
            {/* 可以划分路由: 地址与react组件一一对应起来 */}
            {/* 重定向,通过Navigate 把 / 重定向到 /login */}
            <Route path='/' element={<Navigate to={'/login'}></Navigate>}></Route>
            <Route path='/login' element={<Login></Login>}></Route>
            <Route path='/home' element={<Home></Home>}></Route>
            {/* 404 */}
            <Route path='/*' element={<h1>404</h1>}></Route>
          </Routes>
      </>
      
    )
  }
}

二级的配置

  • 子路由配置,配置在相应的Route下面

    <Route path='/home' element={<Home></Home>}>
        {/* /home下面的二级路由 */}
        <Route path='movie' element={<Movie></Movie>}></Route>
        <Route path='cinema' element={<Cinema></Cinema>}></Route>
    </Route>
    
  • 响应的地方设置路由出口

    import React from 'react'
    // Outlet 类比成vue的 router-view 路由出口
    import { Outlet, Link } from 'react-router-dom'
    
    export default function Home() {
      return (
        <div>
          <Link to={'/home/movie'}>movie</Link>
          <Link to={'/home/cinema'}>cinema</Link>
          <Outlet />
        </div>
      )
    }
    
    

    Outlet为路由出口