React学习时,outlet 路由配置 (prop传参处理,跳转的实现,父子数据共享)

发布时间 2023-06-26 14:44:51作者: 予时光宁静以致远
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
App.js文件
import { HashRouter, Routes, Route } from 'react-router-dom';
import Login from './page/login'
import Loginafter from './page/login/loginafter';
import Loginbefore from './page/login/loginbefore';
import Home from './page/home/home'
import About from './page/about/about'
import Integrated from './page/integrated/integrated'
import Sidebar from './page/sidebar/sidebar'
import Latent from './page/latent/latent'
import Particulars from './page/particulars/particulars'
import SecurityCheck from './page/securityCheck/securityCheck'
function App() {
  {/* 如果需要默认显示页面将不需要填写路径 ,子路由不需要写/斜杠跳转时会带有*/}
  return (
    <HashRouter>
      <Routes>
        <Route path='/' element={<Login />} >
          <Route path='' element={<Loginbefore />} ></Route>
          <Route path='/Loginafter' element={<Loginafter />} ></Route>
        </Route>
        <Route path='/home' element={<Home />} ></Route>
        <Route path='/about' element={<About />} >
          <Route path='' element={<Integrated />} ></Route>
          <Route path='sidebar' element={<Sidebar />} >
            <Route path='' element={<Latent />} ></Route>
            <Route path='particulars' element={<Particulars />} ></Route>
            <Route path='securityCheck' element={<SecurityCheck />} ></Route>
          </Route>
        </Route>
      </Routes>
    </HashRouter>
  );
}

export default App;

关于使用 outlet 会令prop无法准确的传达到子组件,提供案例如下
父组件案例【about.js】
import React from 'react';
import { Outlet, useNavigate } from 'react-router-dom';

export default function About() {
    const navigate = useNavigate();
    // let ddd = [{ sss: 2 }, { bbb: 4 }]
    let ddd = 1;
    const [count, setCount] = React.useState(ddd); //子组件对 setCount 操作后将被此处响应接收
    return (
        <>
            {/* 跳转的同时 重定向默认页 */}
            {/* navigate('integrated', {replace: true}); */}

            {/* 跳转默认页 */}
            {/* navigate('',); */}

            {/* 变更父页   '/'+页名 */}
            {/* navigate('/home') */}

            {/* 跳转同级页  页名  */}
            {/* navigate('sidebar') */}

            {/* 跳转子页面  页名+'/子页名' */}
            {/* navigate('sidebar/securityCheck', { state: ddd }); */}
            {/* 实验,页面跳转时传入的参数,会不会随着刷新丢失? */}
            {/* 结论: 依旧建议数据为接口返回的,当然,接收页(子组件)收到的数据并不会被刷新掉 */}

            {/* 回到上一页 */}
            {/* navigate(-1) */}
            <div>
                <h4>About</h4>
                <button onClick={() => {
                    navigate('test', { state: ddd });
                    // state 传参不限制传参类型
                }}>点击Test页</button>
            </div>

            {/* 以下为渲染子页面 */}
            <Outlet context={[count, setCount]} />
        </>
    )
}

子组件案例【test.js】
import React, { Component } from 'react'
import { useOutletContext } from 'react-router-dom';
// outlet 状态共享 
// 无状态子组件自身发生数据变化,同步给outlet上游组件  本文件以此实现数据共享
const Test = () => {
    const [count, setCount] = useOutletContext(); 
    console.log('count',count) //打印父数据
    //useOutletContext 路由内置方法,监听数据发生变动
    //数据类型任意  由上游 父组件决定
    const increment = () => setCount((c) => c + 1); //操作父数据
    return <button onClick={increment}>{count}</button>;
}
export default Test
在outlet实际摸索中,我的子组件对一则对象的某字段数据进行变更,他发生了报错。
即使outlet 共享了数据/状态,子组件初次渲染也拿到了,但超出局面的状况,可能是由于我有部分知识未能掌握,又或者由其他原因造成的。
查阅了网上一些博主的做法后,我尝试了使用 useEffect 异步更新,然并软。
this.forceUpdate() 强制更新,毕竟我实操变更后数据后,父组件成功打印了它。
然并软。(我猜测可能是子组件为无状态组件缘故,无生命周期导致数据变更的第一时间发生了请求...可以尝试用构造类组件写,当然useOutletContext只支持无状态组件内使用...)
有位博主的做法是,判定虚拟dom元素创建前,获取路由路径,这种做法挺高明的,我也是打算用一则案例写跳转时token的判定机制,和他的这种殊道同归,他也是以一种上下文的实现途径,只不过我是以v6的那种,他的写法上配置全局上下文,有点费时间,早期写过但版本不一致导致我这边报错过,所以我舍弃了旧写法。 但我依旧认为,一定有种很简别的方式实现数据变更,也许只是我没有想到而已...
最终,有一则报错告诉我,我可能需要将数据以数组包对象形式传递给子组件。
于是,我猜测,当我编辑后返回数据时,我可能需要push进去,再删除编辑前的数据,使我的无状态组件渲染时,不至于某行字段报null 或undefined。 至此,附上新案例:
父组件 login 文件夹下的 index.js
import React, { Component, useEffect } from 'react'
import { Outlet, useNavigate, useSearchParams } from 'react-router-dom';

const Btn = () => {
  let state = {
    token: 'asldfjdljfdsojdakf haiksf',
    userName: 'zhangsan',
    password: '123456'
  }

  // let state = 11
  const [grossdata, setgrossdata] = React.useState([state])

  // useEffect(() => {
  //   console.log('异步更新', grossdata)//监听数据发生了更改
  //   console.log('this',this)
  //   // this.forceUpdate()
  //   // if (!grossdata.token) {
  //   //   console.log('判断')
  //   //   // grossdata.token = <></>
  //   //   // this.forceUpdate()
  //   // }
  // }, [grossdata])

  const navigate = useNavigate();
  
  return <>
    <button onClick={() => { navigate('Loginafter') }}>前往登录后</button>
    <Outlet context={[grossdata, setgrossdata]} />
  </>
}

export class index extends Component {
  render() {
    return (
      <>
        <h4>这是管理登录页和登录后页面的文件</h4>
        <Btn></Btn>
      </>
    )
  }
}

export default index
子组件 login 文件夹下的 loginafter.js
import React from 'react'
import { useOutletContext } from 'react-router-dom';

const Loginafter = () => {
    const [grossdata, setgrossdata] = useOutletContext();
    console.log('grossdata子组件打印', grossdata)
    const increment = () => setgrossdata((c) => {
        c[0].token = '';
        let f = c[0]
        return c.push(f) && c.splice(0, 1)
    }); //操作父数据


    return <>
        <div>登录后</div>
        <button onClick={increment}>清除token</button>
        <br></br>
        <span>{grossdata[0].token} </span>
        <br></br>
        <span>{grossdata[0].userName}</span>
        <br></br>
        <span>{grossdata[0].password}</span>
    </>
}
export default Loginafter

我决定尝试,outlet 默认子组件操作 父组件事件...