React多路由页面State管理、传参与组件架构问题咨询
针对你提出的三个核心疑问,直接给出结论和可落地的方案:
疑问1:独立页面组件是否可以维护内部state,是否违背单一数据源原则?
完全可以给独立页面组件维护内部state,这种做法不违背单一数据源设计原则。
单一数据源的核心要求是:同一份跨组件共享的业务状态,只在最合理的上层归属处存唯一一份,不要在多个组件里重复存储、做冗余同步,从来不是要求整个应用只能有一份全局state。
比如News组件里的列表临时hover状态、筛选条件、表单输入值这类只在当前页面内部使用、不需要跨页面/跨组件共享的状态,直接存在对应页面组件的内部state是最优解,比全堆到顶层全局state里更易维护。只有用户信息、全局主题、全站公共方法这类多页面共用的状态/方法,才需要向上提升到根组件或全局状态容器中存储。
疑问2:React Router如何向子页面传递自定义props?将路由配置收拢到App根组件的方案是否合理?
你之前传props失败的核心原因是用法错误:Router、Switch组件本身不会透传自定义属性给子路由,当你使用component={组件名}的写法时,Route渲染组件时只会默认注入match/location/history三个路由内置属性,你挂在Route标签上的其他自定义属性会被Route组件本身接收,不会传递给最终渲染的页面组件。
React Router官方支持两种传自定义props的方案,都可以直接用:
- 方案1(适合少量自定义属性传递):将Route的
component属性替换为render属性,render接收路由内置props作为参数,返回要渲染的组件时,手动展开路由props并追加自定义属性即可。 - 方案2(适合大量全局属性传递):通过React Context在App层包裹全局Provider,所有子页面直接消费Context即可拿到公共方法/状态,不需要每个路由单独传参。
你把路由配置放到App根组件的架构思路是完全合理的,App作为应用根组件,本来就应该承担路由入口、全局公共逻辑挂载的职责,之前的问题只是Route的渲染方式用错了,不是架构方向错了。
疑问3:是否需要将所有业务组件收拢到顶层大组件管理?该架构下如何实现路由对应页面渲染?
不需要把所有业务组件都堆在App根组件里平级渲染——你之前写的<App /> <Header /> <CV /> <News /> <Works />平级结构是错误的,这种写法下不管URL是什么,所有组件都会同时渲染,根本无法实现URL对应页面切换的效果。
正确的顶层架构逻辑非常清晰:
- 全站公共的固定组件(比如Header、全局Footer、侧边栏)直接放在App组件内、
Switch路由容器的外侧,这样路由切换时公共组件会常驻渲染,你也不用每个页面都重复引入一次Header,解决代码冗余问题。 Switch+Route结构放在公共组件下方,只负责渲染当前URL匹配到的唯一一个页面组件,各个页面的业务逻辑还是拆分在独立的组件文件中维护,不需要全部堆到App文件里,保证代码可维护性。
修正后的App.js核心代码
import React from 'react'; import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'; import Header from './Header'; import Introduction from './Introduction'; import Curriculum from './Curriculum'; import Works from './Works'; import News from './News'; import CurrentNews from './CurrentNews'; import NotFound from './NotFound'; class App extends React.Component { yellAtMe = (fish) => { console.log('heeeeeeeeeeeeeeeeeee'); }; render() { return ( <Router> {/* 公共Header放在Switch外,全页面共用,无需每个页面单独引入 */} <Header /> <Switch> {/* 用render替代component写法,同时透传路由props和自定义props */} <Route exact path="/" render={(routeProps) => <Introduction {...routeProps} yellAtMe={this.yellAtMe} />} /> <Route exact path="/curriculum" render={(routeProps) => <Curriculum {...routeProps} yellAtMe={this.yellAtMe} />} /> <Route exact path="/works" render={(routeProps) => <Works {...routeProps} yellAtMe={this.yellAtMe} />} /> <Route exact path="/news" render={(routeProps) => <News {...routeProps} yellAtMe={this.yellAtMe} />} /> <Route path="/news/:newsId" render={(routeProps) => <CurrentNews {...routeProps} yellAtMe={this.yellAtMe} />} /> <Route render={(routeProps) => <NotFound {...routeProps} yellAtMe={this.yellAtMe} />} /> </Switch> </Router> ); } } export default App;
改完之后,你可以把Curriculum、News等页面组件里重复引入的<Header />标签删掉,直接保留页面主体内容即可。所有页面组件都可以通过this.props.yellAtMe访问到App里定义的公共方法,原有的this.props.history.push跳转逻辑也完全不受影响。
内容的提问来源于stack exchange,提问作者balintov

