typescript结合react+electron桌面应用程序的实践(二)—路由篇

或许我们已经学会了最基本的配置,如何配置react路由、如何启动electron+react项目、typescript的描述文件、如果能掌握一点typescript的基本知识,再配置一点webpack【之前的博客】的基本loader,webpack的开发环境、生产环境配置,最最最基本的骨架已经可以修改和配置了,只需要在需要的时候查缺补漏就可以,剩下的事情或许就是写页面了。

react-router 4版本还是有很多注意的地方,代码就像情人,一段时间不见,就没感觉了,所以还是再熟悉一遍了。

本篇文章主要是转发,然后在原来的基础上进行实践和补充,原文链接

React Router被拆分成三个包:react-router,react-router-domreact-router-nativereact-router提供核心的路由组件与函数。其余两个则提供运行环境(即浏览器与react-native)所需的特定组件。

进行网站(将会运行在浏览器环境中)构建,我们应当安装react-router-domreact-router-dom暴露出react-router中暴露的对象与方法,因此你只需要安装并引用react-router-dom即可。

  1. 安装。我们或许已经习惯了npm install XXX,但是别忘记,我们这一次是使用的typescript,所以在安装的时候要注意依赖包的描述文件,否则就需要我们单独配置.d.ts了,当然,这个时候我们已经学会了,也没关系。
    一般的npm包都会有相应的.d.ts文件,所以我们在安装的时候只需要加上前缀@types/即可,比如

    npm install --save @types/react-router-dom

    这样就可以直接在tsx页面文件中使用了。

  2. 是否还记得路由类型?比如我们在vue的使用过程中,有三种路由类型可以选择【history】、【hash】、【abstract】,react-router中也是可以选择模式的,实际上已经封装成了组件,<BrowserRouter><HashRouter>,当存在服务区来管理动态请求时,需要使用<BrowserRouter>组件,而<HashRouter>被用于静态网站。
  3. 【History】。每个路由器都会创建一个history对象并用其保持追踪当前location并且在有变化时对网站进行重新渲染。这个history对象保证了React Router提供的其他组件的可用性,所以其他组件必须在router内部渲染。一个React Router组件如果向父级上追溯却找不到router组件,那么这个组件将无法正常工作。
  4. 渲染Router。
    脚手架搭建的项目中我们可以看到src文件夹下面有index.tsx、App.tsx,通过引用关系,不难看出两者的关系。

    把我们的APP组件用BrowserRouter包裹,这样子就可以让页面内的所有页面支持路由跳转,否则这个组件之外的是不支持router的。再来看看APP.tsx

    是不是发现我们把页面分成了三部分,【头部】【中间content】【尾部】头部和尾部是固定的样式容器,当路由改变的时候,只改变中间content的组件切换就可以。
    而RouterConfig,就是我们定义的router文件夹下面集中管理我们路由的地方,至少我是觉得,集中管理的路由比这里一坨那里一块的定义会好维护一些
  5. Route。<Route>组件是React Router中主要的结构单元。在任意位置只要匹配了URL的路径名(pathname)你就可以创建<Route>元素进行渲染。
      render() {
        return (
          <Switch>
            <Route path="/" exact component={home} />
            <Route path="/home" component={home} />
            <Route path="/user/:id" component={User} />
            <Route path="/test-ui" component={TestUi} />
          </Switch>
        )
      }

    就像这样,我们只需要定义好我们的path还有对应的component,就可以在页面URL改变的时候,访问不同的组件页面,或许我应该在隔壁开一篇记录ts的文章,同步走。但是我们有时候需要传递props,就需要稍微改变一下写法了,这样写显得更容易看一些。

    千万要记得你的Login里面要定义好loginStatus的接口哦!不然你就再定义一次

    interface Props {
      loginStatus: boolean;
    }
    class Login extends React.Component<Props> {
     ...
    }

    或许你现在配置正确的话,可以改变你的url路径来看看页面有没有响应,甚至可以做到根据props的改变来动态的显示login的内容

  6. Path。<Route>接受一个数为string类型的path。例如:<Route path='/roster'/>会匹配以/roster开头的路径名。在当前path参数与当前location的路径相匹配时,路由就会开始渲染React元素。若不匹配,路由不会进行任何操作。
    <Route path='/roster'/>
    // 当路径名为'/'时, path不匹配
    // 当路径名为'/roster'或'/roster/2'时, path匹配
    // 当你只想匹配'/roster'时,你需要使用"exact"参数
    // 则路由仅匹配'/roster'而不会匹配'/roster/2'
    <Route exact path='/roster'/>

    还是需要特别注意的,比如当你想要一个动态path的时候,exact可以不要,当你需要完全匹配的时候,则置为true

  7. Switch。或许你已经注意到,我们的route组件是被switch组件包裹的,这里的switch组件可以遍历自己的子元素(所有route),并且对第一个匹配当前路径的route进行页面渲染。一般而言,我们都是"/",
  8. Route发生了什么?
    当我们的路由的path和当前的url路径匹配成功的时候,就会根据route的三个属性来渲染Component、render、children

    <Route path='/login' component={login} />
    <Route path='/login' render={(props) => (
      <Page {...props} data={extraProps}/>
    )}/>
    <Route path='/page' children={(props) => (
      props.match
        ? <Page {...props}/>
        : <EmptyPage {...props}/>
    )}/>

    再或者,你可以直接

    <Route path="/" exact={true}>
       <Home name="首页"/>
    </Route>

    或许都可以尝试一下

    其实这里的写法还是需要我们根据不同的场景,来灵活应用的。

  9. 嵌套路由。实际的应用场景中,我们基本上不存在单一的一个页面,路由基本上都是由多层嵌套的组件形成,并且很多时候我们需要动态的改变URL的路径,也会相应的动态匹配组件
    设想我们有一个歌单组件,但是我们歌单又有很多的类别,这个时候其实就需要我们去嵌套路由来实现这种场景,带类别的歌单是歌单的子级,但是类别之间切换的时候,路由还是离不开歌单,比如:/songsheet/:types,不管types如何变化,都是由/songsheet来匹配,我们看看具体实现
    在我们的路由入口处定义歌单path

    <Route path="/songsheet/:type" component={SongSheet}/>

    紧接着在SongSheet中定义不同的组件切换,也可以试着试用一下switch或者三目,一般而言,这里的样式都是一样的,只不过需要不同的筛选条件切换显示的数据即可,我们这里这是为了举例嵌套路由所以这么写。不要被demo限制。

  10. 路径参数
    有时路径名中存在我们需要获取的参数,比如上面例子的歌单类型,眼尖的或许已经可以发现获取的方式,我们的动态路由在切换的时候,比如你的路径是"/songsheet/:type",那么这个type就会自动塞到路径对象match的params下面,即:props.match.params.type,当然说什么都不如把props打印出来一看究竟,甚至可以发现path和url的关系等等
  11. Link。
    之前对vue路由熟悉的是不是有个疑问,我在vue中,可以用router.push、router.replace、router-link来实现路由之间的跳转,而react也是妥妥的没问题的。
    Link组件并不像是a标签,URL会更新,组件会被重新渲染,但是页面不会重新加载。

    import { Link } from 'react-router-dom'
    const Header = () => (
      <header>
        <nav>
          <ul>
            <li><Link to='/'>Home</Link></li>
            <li><Link to='/login'>Roster</Link></li>
            <li><Link to='/songsheet/sad'>Schedule</Link></li>
          </ul>
        </nav>
      </header>
    )

    当然,路径参数也是没问题:

    <li><Link to="/login?name=我">首页</Link></li>

    还可以把页面惨location属性分开赋值:

              <li><Link
                  to={{
                    pathname: "/login",
                    search: "?name=我",
                    hash: "#login-hash",
                    state: { fromDashboard: true }
                  }}>首页</Link>
              </li>

    还有增加replace属性,让它直接把之前的路径覆盖,变成一个新的:实际上在封装的时候,底层用了history.replace

    <li><Link to="/login" replace></Link></li>

    或者增加ref:

    当结合ts使用的时候,一定要注意定义变量的时候要进行类型检查, 关于ref,就是我们直接操作DOM结构了,一般在用户输入的时候,聚焦失焦的时候,获取值,可以打印一下看看我们的node

  12. 如果我不想用Link呢?我想用vue一样的编程式路由,应该怎么弄呢?问题不大。基本上都是基于window.history来实现的
    在react-router V3版本中, 是提供了一个组件browserHistory,而我们可以通过browserHistory.push("/XXX")来实现跳转

    import browserHistory from 'react-router';
    
    export function addProduct(props) {
      return dispatch =>
        axios.post(`xxx`, props, config)
          .then(response => {
            browserHistory.push('/cart'); //这里
          });
    }

    但是我们已经是V4了!已经没有这个组件了,但还是有别的办法滴~
    1. 高阶组件withRouter。官方推荐的做法

    import {withRouter} from "react-router-dom";
    
    withRouter(Login)

    官网的例子是直接用高阶组件包裹我们定义好的组件,然后export,但是我们用了TS。

    实际上,高阶组件的调用实际上还是高阶函数的函数调用,所以高阶组件的使用,我们既可以使用函数式方法调用,也可以使用装饰器。但是在TS中,编译器会对装饰器作用的值做签名一致性校验,但是高阶组件调用的时候,实际上是返回一个新的组件,并且会对被作用的组件的props 进行修改(新增、删除)等,所以会导致签名校验失败,TS报错。
    这里我还没整明白。。。渡劫失败,先给出js版本的, 事实上,如果不使用typescript的时候,官方推荐的做法还是很管用的

    import React from "react";
    import {withRouter} from "react-router-dom";
    
    class MyComponent extends React.Component {
      constructor(props) {
        super(props)
        this.myFunction = this.myFunction.bind(this)
      }
      ...
      myFunction() {
        this.props.history.push("/home");
      }
      ...
    }
    export default withRouter(MyComponent);

    实际上我们还可以通过自己新建一个history来实现跳转:

    // src/history.js
    import createHistory from 'history/createBrowserHistory'
    
    export default createHistory()

    然后在我们的index.js,修改

    import history from './untils/history'
    
    const render = Component => {
      ReactDOM.render(
        <Router history={history}>
          <Component />
        </Router>
        , document.getElementById('root'))
    }

    相当于在最外层的Router组件配置了一个history,然后router下面的所有路由都是可以直接跳转的,在组件内部

    import history from '../../untils/history'
    
    class ReviseCenter extends React.Component{
      handleHistory () {
        history.push('/home')
      }
    }

    这样实现也是完全没问题的,而且没有withRouter那么复杂,或许我们可以试试typescript使用这种方法, 事实证明,完全没问题啊!

  13. 其实react-router还是有很多东西的,但是最最最常用的我们已经掌握,其余的API可以看看官方的文档,还是会有很多的知识点

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注