React 脚手架介绍以及路由基本信息

create-react-app创建项目

npm install -g create-react-app
create-react-app react-cli

目录结构介绍

图中红色框是我后来加的,暂时可以不考虑。


public:里面包含了我们项目中的启动页面,react比较适合单页面项目应用开发。
  • favico.ico: 这是用来表示:快捷方式 小图标。详情可以访问文章
  • index.html: 初始页面。
  • manifest.json: wepack打包优化相关,本人没有研究过。
src:里面包含项目文件,最重要的是下面两个文件
  • index.js 项目初始执行的js。内部进行页面元素的渲染操作。
  • App.js index.js初始加载的组件。

至于为什么初始加载的index.html和index.js。我们可以将项目中的配置文件显示出来

在当前目录下执行下面代码:

npm run eject //必须在当前本地没有code change的情况下去执行,不然可能会报错,因为我在用的时候就报错了。

然后我们会看到目录结构发生变化:


如图两个红色框都是新加的。不过目前我最关心的是path.js。下面是部分代码:

module.exports = {
  dotenv: resolveApp('.env'),
  appBuild: resolveApp('build'),
  appPublic: resolveApp('public'),
  appHtml: resolveApp('public/index.html'),
  appIndexJs: resolveApp('src/index.js'),
  appPackageJson: resolveApp('package.json'),
  appSrc: resolveApp('src'),
  yarnLockFile: resolveApp('yarn.lock'),
  testsSetup: resolveApp('src/setupTests.js'),
  appNodeModules: resolveApp('node_modules'),
  publicUrl: getPublicUrl(resolveApp('package.json')),
  servedPath: getServedPath(resolveApp('package.json')),
};
如图可以看出为什么index.html和index.js是初始执行文件。

路由例子

提出路由问题

单页面应用上的hash值变化会导致页面上渲染上不同的elements。

第一需要监控hash值的变化。

第二hash值的变化需要引起当前主组件内的state变化。(这一点相信不难理解,可以访问前一篇文章)。

不使用React Router

//index.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'))
//App.js
import React, {Component} from 'react';
import logo from './logo.svg';
import './App.css';
import About from "./containers/about";
import Inbox from "./containers/inbox";
import Home from "./containers/home";

class App extends Component {
    constructor(props) {
        super(props);
        this.state = {route: window.location.hash.substr(1)};
    }

    componentDidMount() {
        window.addEventListener('hashchange', () => { //监控hash值的变化
            this.setState({
                route: window.location.hash.substr(1) //更新state值
            })
        })
    }

    render() {
        let Child
        switch (this.state.route) { //判断state值,渲染不同的组件
            case '/about':
                Child = About;
                break;
            case '/inbox':
                Child = Inbox;
                break;
            default:
                Child = Home;
        }

        return (
            <div>
                <h1>App</h1>
                <ul>
                    <li><a href="#/about">About</a></li>
                    <li><a href="#/inbox">Inbox</a></li>
                    <li><a href="#/home">Home</a></li>
                </ul>
                <Child/>
            </div>
        )
    }
}

export default App;
// containers/about.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class About extends Component {
    render() {
        return (
            <div>About</div>
        );
    }
}

export default About;


// containers/home.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class Home extends Component {
    render() {
        return (
            <div>Home</div>
        );
    }
}

export default Home;


// containers/inbox.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class Inbox extends Component {
    render() {
        return (
            <div>Inbox</div>
        );
    }
}

export default Inbox;
源码地址: https://github.com/rodchen-king/react/tree/v0.1/react-cli

React-Route

React Router 是一个基于 React 之上的强大路由库,它可以让你向应用中快速地添加视图和数据流,同时保持页面与 URL 间的同步。

npm install react-router@2.8.1 --save

此处说明一下:因为当前的react版本是16.3.2,因为react16的不能兼容 react-router 2/3, 如果要用 react-router2/3,需将react降级到15,所以我这里用的react@15.6.2,react-dom@15.6.2。

举例:

import React from 'react';
import ReactDOM from 'react-dom'
import { Router, Route, Link } from "react-router";
import './index.css';

const App = React.createClass({
    render() {
        return (
            <div>
                <h1>App</h1>
                <ul>
                    <li><Link to="/about">About</Link></li>
                    <li><Link to="/inbox">Inbox</Link></li>
                </ul>
                {this.props.children}
            </div>
        )
    }
})

const About = React.createClass({
    render() {
        return <h3>About</h3>
    }
})

const Inbox = React.createClass({
    render() {
        return (
            <div>
                <h2>Inbox</h2>
                {this.props.children || "Welcome to your Inbox"}
            </div>
        )
    }
})

const Message = React.createClass({
    render() {
        return <h3>Message {this.props.params.id}</h3>
    }
})

ReactDOM.render((
    <Router>
        <Route path="/" component={App}>
            <Route path="about" component={About} />
            <Route path="inbox" component={Inbox}>
                <Route path="messages/:id" component={Message} />
            </Route>
        </Route>
    </Router>
), document.getElementById('root'))

路由基本配置

ReactDOM.render((
    <Router>
        <Route path="/" component={App}>
            <Route path="about" component={About} />
            <Route path="inbox" component={Inbox}>
                <Route path="messages/:id" component={Message} />
            </Route>
        </Route>
    </Router>
), document.getElementById('root'));

Router

React Router 的重要组件。它能保持UI和URL的同步,其内部包含多个路由。

children (required)

一个或多个的 Route 或 PlainRoute。当 history 改变时, <Router> 会匹配出 Route 的一个分支,并且渲染这个分支中配置的组件,渲染时保持父 route 组件嵌套子 route 组件。

routes
children 的别名。

const routes = (<Route path="/" component={App}>
    <Route path="about" component={About}/>
    <Route path="inbox" component={Inbox}>
        <Route path="messages/:id" component={Message}/>
    </Route>
</Route>);

ReactDOM.render((
    <Router children={routes} routes={routes}>

    </Router>
), document.getElementById('root'))
对于children和routes之间的区别没有研究过,<Router children={routes} routes={routes}>二者任意一个存在,路由都可以运行。 还有其他的props,这种就需要自己拓展了。

Route

Route 是用于声明路由映射到应用程序的组件层。
<Route path="/" component={App}>
    <Route path="about" component={About} />
    <Route path="inbox" component={Inbox}>
        <Route path="messages/:id" component={Message} />
    </Route>
</Route>
path
URL 中的路径。它会以父组件路由为基础,除非它是从/开始的, 将它变成一个绝对路径。
父子组件如何判断,可以看出<Route></Route>内部的Route组件都是可能出现的子组件。
<Route path="/App" component={App}>
    <Route path="about" component={About} />
    <Route path="/inbox" component={Inbox}>
        <Route path="messages/:id" component={Message} />
    </Route>
</Route>

这个例子中中,/App/about可以访问到About组件,但是/App/inbox访问不到InBox组件。因为路由path以/开头。

component
当匹配到URL时,单个的组件会被渲染。它可以被父route 组件的this.props.children渲染。这个知识点很重要,因为父子组件通过路由渲染的过程中,是需要一个地方渲染对应组件内容。所以每个父组件都会设置this.props.children来划定一个渲染子组件的区域。

const App = React.createClass({
    render() {
        return (
            <div>
                <h1>App</h1>
                <ul>
                    <li><Link to="/about">About</Link></li>
                    <li><Link to="/inbox">Inbox</Link></li>
                </ul>
                {this.props.children}
            </div>
        )
    }
})
components
Route 可以定义一个或多个已命名的组件,当路径匹配到 URL 时,它们作为 name:component 对的一个对象去渲染。它们可以被父route组件的this.props[name] 渲染。
这里的含义是:子路由的变化会导致父组件的多处变化。下面例子是父组件中有两块不相接的区域会随着路由的变化而变化。这里其实将子组件按照路由的变化然后传入到父组件的props中。

import React from 'react';
import ReactDOM from 'react-dom'
import {Router, Route, Link} from "react-router";
import './index.css';

const App = React.createClass({
    render() {
        const { main, sidebar } = this.props
        return (
            <div>
                <h1>App</h1>
                <div className="Main">
                    {main}
                </div>
                <ul>
                    <li><Link to="/groups">groups</Link></li>
                    <li><Link to="/users">users</Link></li>
                </ul>
                <div>
                    <div className="Sidebar">
                        {sidebar}
                    </div>
                </div>
            </div>
        )
    }
})

const Groups = React.createClass({
    render() {
        return <h3>Groups</h3>
    }
})

const GroupsSidebar = React.createClass({
    render() {
        return (
            <div>
                <h2>GroupsSidebar</h2>
            </div>
        )
    }
})

const Users = React.createClass({
    render() {
        return <h3>Users</h3>
    }
})

const UsersSidebar = React.createClass({
    render() {
        return (
            <div>
                <h2>UsersSidebar</h2>
                <h3>{this.props.children}</h3>
            </div>
        )
    }
})

const Profile = React.createClass({
    render() {
        return (
            <div>
                <h2>Profile {this.props.params.id}</h2>
            </div>
        )
    }
})

const routes = (
    <Route path='/' component={App}>
        <Route path="groups" components={{main: Groups, sidebar: GroupsSidebar}}/>
        <Route path="users" components={{main: Users, sidebar: UsersSidebar}}>
            <Route path="users/:userId" component={Profile}/>
        </Route>
    </Route>
)

ReactDOM.render((
    <Router children={routes}>
    </Router>
), document.getElementById('root'))

Link

允许用户浏览应用的主要方式。<Link> 以适当的 href 去渲染一个可访问的锚标签。
<Link> 可以知道哪个 route 的链接是激活状态的,并可以自动为该链接添加 activeClassName 或 activeStyle。
to
跳转链接的路径,如 /users/123。
query
已经转化成字符串的键值对的对象。
hash
URL 的 hash 值,如 #a-hash。
注意:React Router 目前还不能管理滚动条的位置,并且不会自动滚动到 hash 对应的元素上。如果需要管理滚动条位置,可以使用 scroll-behavior 这个库。
state
保存在 location 中的 state。
activeClassName
当某个 route 是激活状态时,<Link> 可以接收传入的 className。失活状态下是默认的 class。
activeStyle
当某个 route 是激活状态时,可以将样式添加到链接元素上。

添加首页

首页就是父组件初始渲染的页面,也可以说是父组件的默认渲染页面,使用 IndexRoute 来设置一个默认页面。IndexRoute对应的组件将会渲染到App中的this.props.children处。
<Route path="/" component={App}>
    <IndexRoute component={Dashboard} />
    <Route path="about" component={About}/>
    <Route path="inbox" component={Inbox}>
        <Route path="messages/:id" component={Message}/>
    </Route>
</Route>

页面跳转

当我们修改了url之后,如下访问messages/1的路由从/inbox/messages/:id -> /messages/:id,这个时候为了兼容旧的url,则需要使用Redirect:
<Route path="/" component={App}>
    <IndexRoute component={Dashboard} />
    <Route path="about" component={About}/>
    <Route path="inbox" component={Inbox}>
        <Route path="/messages/:id" component={Message}/>
        <Redirect from="messages/:id" to="/messages/:id" />
    </Route>
</Route>

进入和离开的Hook

Route 可以定义 onEnter 和 onLeave 两个 hook ,这些hook会在页面跳转确认时触发一次。这些 hook 对于一些情况非常的有用,例如权限验证或者在路由跳转前将一些数据持久化保存起来。但是下面例子中我的测试,二者不不能共存,如果二者同时存在,onLeave没有作用。
import React from 'react';
import ReactDOM from 'react-dom'
import {Router, Route, Link} from "react-router";
import './index.css';
import IndexRoute from "react-router/es6/IndexRoute";
import Redirect from "react-router/es6/Redirect";

const App = React.createClass({
    render() {
        return (
            <div>
                <h1>App</h1>
                <ul>
                    <li><Link to="/about">About</Link></li>
                    <li><Link to="/inbox">Inbox</Link></li>
                </ul>
                {this.props.children}
            </div>
        )
    }
})

const About = React.createClass({
    render() {
        return <h3>About</h3>
    }
})

const Inbox = React.createClass({
    render() {
        return (
            <div>
                <h2>Inbox</h2>
                {this.props.children || "Welcome to your Inbox"}
            </div>
        )
    }
})

const Message = React.createClass({
    render() {
        return <h3>Message {this.props.params.id}</h3>
    }
})

const Dashboard = React.createClass({
    render() {
        return <h3>Hello!</h3>
    }
})

 const EnterHook = function(RouterState, RedirectFunction, callback) {
    console.log("进入about");
}

const LeaveHook = function(RouterState) {
    console.log("离开about");
}

const routes = (
    <Route path="/" component={App}>
        <IndexRoute component={Dashboard} />
        <Route path="about" onLeave={LeaveHook} onEnter={EnterHook}  component={About}/>
        <Route path="inbox" component={Inbox}>
            <Route path="/messages/:id" component={Message}/>
            <Redirect from="messages/:id" to="/messages/:id" />
        </Route>
    </Route>);

ReactDOM.render((
    <Router children={routes}>
    </Router>
), document.getElementById('root'))

替换的配置方式

因为 route 一般被嵌套使用,所以使用 JSX 这种天然具有简洁嵌套型语法的结构来描述它们的关系非常方便。然而,如果你不想使用 JSX,也可以直接使用原生 route 数组对象。
const routes = [
    { path: '/',
        component: App,
        indexRoute: { component: Dashboard },
        childRoutes: [
            { path: 'about', component: About },
            { path: 'inbox',
                component: Inbox,
                childRoutes: [
                    { path: '/messages/:id', component: Message }
                ]
            }
        ]
    }
]

路由匹配

<Route path="/hello/:name">         // matches /hello/michael and /hello/ryan
<Route path="/hello(/:name)">       // matches /hello, /hello/michael, and /hello/ryan
<Route path="/files/*.*">           // matches /files/hello.jpg and /files/hello.html
<Route path="/**/*.jpg">            // matches /files/hello.jpg and /files/path/to/file.jpg

Index Links

IndexLink的意义在于只有当前的路由匹配的情况下才会显示激活状态,下面举个例子:路由匹配到/inbox,此时下面第一个和第三个link都显示已激活。但是第二个不会,因为当前的路由是/inbox,不是/。只有当路由为/,第二个link才会激活。
<ul>
    <li><Link activeClassName='active' to="/">About</Link></li>
    <li><IndexLink activeClassName='active' to="/">About</IndexLink></li>
    <li><Link activeClassName='active' to="/inbox">Inbox</Link></li>
</ul>

browserHistory

React Router 是建立在 history 之上的。 简而言之,一个 history 知道如何去监听浏览器地址栏的变化, 并解析这个 URL 转化为 location 对象, 然后 router 使用它匹配到路由,最后正确地渲染对应的组件。

常用的 history 有三种形式, 但是你也可以使用 React Router 实现自定义的 history。

browserHistory
hashHistory

createMemoryHistory

在我没有使用任何的方式的情况下,路由形如下面:

http://localhost:3000/inbox#/?_k=iawvme

然后修改代码如下,加上history={browserHistory}

<Router history={browserHistory} children={routeConfig}>

路由变化就是:http://localhost:3000/inbox


动态路由

React Router 里的路径匹配以及组件加载都是异步完成的,不仅允许你延迟加载组件,并且可以延迟加载路由配置。在首次加载包中你只需要有一个路径定义,路由会自动解析剩下的路径。
Route 可以定义 getChildRoutes,getIndexRoute 和 getComponents 这几个函数。它们都是异步执行,并且只有在需要时才被调用。我们将这种方式称之为 “逐渐匹配”。 React Router 会逐渐的匹配 URL 并只加载该 URL 对应页面所需的路径配置和组件。
const CourseRoute = {
  path: 'course/:courseId',

  getChildRoutes(location, callback) {
    require.ensure([], function (require) {
      callback(null, [
        require('./routes/Announcements'),
        require('./routes/Assignments'),
        require('./routes/Grades'),
      ])
    })
  },

  getIndexRoute(location, callback) {
    require.ensure([], function (require) {
      callback(null, require('./components/Index'))
    })
  },

  getComponents(location, callback) {
    require.ensure([], function (require) {
      callback(null, require('./components/Course'))
    })
  }
}

这里的思想很简单,我们来类比一下:

const routeConfig = [
    { path: '/',
        component: App,
        indexRoute: { component: Dashboard },
        childRoutes: [
            { path: 'about', component: About },
            { path: 'inbox',
                component: Inbox,
                childRoutes: [
                    { path: '/messages/:id', component: Message }
                ]
            }
        ]
    }
]
我们以/为例:getChildRoutes方法回去访问子路由的文件,首先说明这里的子路由文件是一个路由,不是一个简单的组件。子路由文件的格式和上面的类似,也可能会有这几个方法。另外两个方法访问的就是具体的组件了。

跳转前确认

React Router 提供一个 routerWillLeave 生命周期钩子,这使得 React 组件可以拦截正在发生的跳转,或在离开 route 前提示用户。routerWillLeave 返回值有以下两种:

return false 取消此次跳转

return 返回提示信息,在离开 route 前提示用户进行确认。

需要先引入mixins: [ Lifecycle ]

const About = React.createClass({
    mixins: [ Lifecycle ],

    routerWillLeave(nextLocation) {
        return 'Your work is not saved! Are you sure you want to leave?'
    },

    render() {
        return <h3>About</h3>
    }
})

获取路由参数

组件的生命周期内我们可以通过this.props.params获取到。



写在最后:react 路由知识还有许多东西需要探索,但是可以先了解基本知识再去拓展,对于版本的升级可以上网简单搜索一下,应该就可以解决了。最后吐槽一下react的生态圈真的不如angular。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值