官網https://github.com/reactjs/react-router-tutorial學習

01-setting-up

from https://github.com/reactjs/react-router-tutorial/tree/master/lessons/01-setting-up
Clone the Tutorial


git clone https://github.com/reactjs/react-router-tutorial
cd react-router-tutorial
cd lessons/01-setting-up
npm install
npm start
Now open up http://localhost:8080

運行環境Ok了,仔細讀一下相關代碼及關聯,這一節無太多知識點,問題1,8080從哪裏配置出來的

02-rendering-a-route

vi index.js

// insert into index.js
import About from './modules/About'
import Repos from './modules/Repos'

render((
  <Router history={hashHistory}>
    <Route path="/" component={App}/>
    {/* add the routes here */}
    <Route path="/repos" component={Repos}/>
    <Route path="/about" component={About}/>
  </Router>
), document.getElementById('app'))
// modules/About.js
import React from 'react'

export default React.createClass({
  render() {
    return <div>About</div>
  }
})
// modules/Repos.js
import React from 'react'

export default React.createClass({
  render() {
    return <div>Repos</div>
  }
})

注意,運行後界面上出不來啥
Now visit http://localhost:8080/#/about and http://localhost:8080/#/repos

03-navigating-with-link

// modules/App.js
import React from 'react'
import { Link } from 'react-router'

export default React.createClass({
  render() {
    return (
      <div>
        <h1>React Router Tutorial</h1>
        <ul role="nav">
          <li><Link to="/about">About</Link></li>
          <li><Link to="/repos">Repos</Link></li>
        </ul>
      </div>
    )
  }
})

運行時界面出來了,感覺modules的App.js是個總的模塊,調用其他的模塊,作view展示用,根目錄下的index.js作來做好route關聯,
到了06發現這個理解不對的 index.js調用各個modules,如有的modules裏是展示內容的話,就按先後調用展示出來而已

04-nested-routes

// index.js
// ...
render((
  <Router history={hashHistory}>
    <Route path="/" component={App}>
      {/* make them children of `App` */}
      <Route path="/repos" component={Repos}/>
      <Route path="/about" component={About}/>
    </Route>
  </Router>
), document.getElementById('app'))
// modules/App.js
// ...
  render() {
    return (
      <div>
        <h1>React Router Tutorial</h1>
        <ul role="nav">
          <li><Link to="/about">About</Link></li>
          <li><Link to="/repos">Repos</Link></li>
        </ul>

        {/* add this */}
        {this.props.children}

      </div>
    )
  }
// ...

問題1: 爲何一直首頁下面直接顯示repos.js中的內容?

A: 這是因爲有個hashHistory在裏面,最後一次點開的是repos,所以展示這個,再用F5刷都沒用
## 問題2:如果重啓一下node,啥都沒有點的時候,出來的是啥?
A:如果上次最後一次點的是about, 重啓Node,刷新第一次,顯示的還是about的內容,
## 問題3:試着對着其他的文檔,換成browserHistory【生產項目都用這個,其他的不用,爲啥】,這個不出現_k的hash鍵值對,出不出有啥關係?
A: 換成browserHistory 切記留意10中提到的幾處修改,一個是package.json,一個是index.html調用絕對路徑

05-active-links

Active Links

One way that Link is different from a is that it knows if the path it links to is active so you can style it differently.

Active Styles

Let's see how it looks with inline styles, add activeStyle to your Links.

// modules/App.js
<li><Link to="/about" activeStyle={{ color: 'red' }}>About</Link></li>
<li><Link to="/repos" activeStyle={{ color: 'red' }}>Repos</Link></li>
Now as you navigate, the active link is red.

Active Class Name

You can also use an active class name instead of inline-styles.

// modules/App.js
<li><Link to="/about" activeClassName="active">About</Link></li>
<li><Link to="/repos" activeClassName="active">Repos</Link></li>
We don't have a stylesheet on the page yet though. Lets add one-extra point if you can add a link tag from memory.

// index.html
<link rel="stylesheet" href="index.css" />
And the CSS file:

.active {
  color: green;
}
You'll need to manually refresh the browser since Webpack isn't building our index.html.

Nav Link Wrappers

Most links in your site don't need to know they are active, usually just primary navigation links need to know. It's useful to wrap those so you don't have to remember what your activeClassName or activeStyle is everywhere.

We will use a spread operator here, the three dots. It clones our props and in this use case it clones activeClassName to our desired component for us to benefit from.

Create a new file at modules/NavLink.js that looks like this:

// modules/NavLink.js
import React from 'react'
import { Link } from 'react-router'

export default React.createClass({
  render() {
    return <Link {...this.props} activeClassName="active"/>
  }
})
Now you can go change your links to NavLinks.

// modules/App.js
import NavLink from './NavLink'

// ...

<li><NavLink to="/about">About</NavLink></li>
<li><NavLink to="/repos">Repos</NavLink></li>
Oh, how beautiful upon the renders is the composability of components.

問題1 NavLink.js 裏面有一行

問題2 還是這行,這裏的 active與css裏的一樣,通過這個方式讓 react與css聯動了?【當然 index.html裏有調用】

06-params

Adding a Route with Parameters

Let's teach our app how to render screens at /repos/:userName/:repoName.

First we need a component to render at the route, make a new file at modules/Repo.js that looks something like this:

// modules/Repo.js
import React from 'react'

export default React.createClass({
  render() {
    return (
      <div>
        <h2>{this.props.params.repoName}</h2>
      </div>
    )
  }
})
Now open up index.js and add the new route.

// ...
// import Repo
import Repo from './modules/Repo'

render((
  <Router history={hashHistory}>
    <Route path="/" component={App}>
      <Route path="/repos" component={Repos}/>
      {/* add the new route */}
      <Route path="/repos/:userName/:repoName" component={Repo}/>
      <Route path="/about" component={About}/>
    </Route>
  </Router>
), document.getElementById('app'))
Now we can add some links to this new route in Repos.js.

// Repos.js
import { Link } from 'react-router'
// ...
export default React.createClass({
  render() {
    return (
      <div>
        <h2>Repos</h2>

        {/* add some links */}
        <ul>
          <li><Link to="/repos/reactjs/react-router">React Router</Link></li>
          <li><Link to="/repos/facebook/react">React</Link></li>
        </ul>

      </div>
    )
  }
})
Now go test your links out. Note that the parameter name in the route path becomes the property name in the component. Both repoName and userName are available on this.props.params of your component. You should probably add some prop types to help others and yourself out later.

頁面中,點about只是出來about內容,點repos,就把repos裏的調用全展示出來了。又是一個路由展示

07-more-nesting

More Nesting

Notice how the list of links to different repositories goes away when we navigate to a repository? What if we want the list to persist, just like the global navigation persists?

Try to figure that out before reading on.

...

First, nest the Repo route under the Repos route. Then go render this.props.children in Repos.

// index.js
// ...
<Route path="/repos" component={Repos}>
  <Route path="/repos/:userName/:repoName" component={Repo}/>
</Route>
// Repos.js
// ...
<div>
  <h2>Repos</h2>
  <ul>
    <li><Link to="/repos/reactjs/react-router">React Router</Link></li>
    <li><Link to="/repos/facebook/react">React</Link></li>
  </ul>
  {/* will render `Repo.js` when at /repos/:userName/:repoName */}
  {this.props.children}
</div>
Active Links

Let's bring in our NavLink from before so we can add the active class name to these links:

// modules/Repos.js
// import it
import NavLink from './NavLink'

// ...
<li><NavLink to="/repos/reactjs/react-router">React Router</NavLink></li>
<li><NavLink to="/repos/facebook/react">React</NavLink></li>
// ...
Notice how both the /repos link up top and the individual repo links are both active? When child routes are active, so are the parents.

我把大段代碼和文字都放在代碼段的原因,是我自己發現,有些不是代碼,但是變量啥的,在網頁上覆制會變, 比如 npm install –save react 就會變成 -save 少這麼一個橫,比較奇怪

08-index-routes

When we visit / in this app it's just our navigation and a blank page. We'd like to render a Home component there. Lets create a Home component and then talk about how to render it at /.

// modules/Home.js
import React from 'react'

export default React.createClass({
  render() {
    return <div>Home</div>
  }
})
One option is to see if we have any children in App, and if not, render Home:

// modules/App.js
import Home from './Home'

// ...
<div>
  {/* ... */}
  {this.props.children || <Home/>}
</div>
//...
This would work fine, but its likely we'll want Home to be attached to a route like About and Repos in the future. A few reasons include:

Participating in a data fetching abstraction that relies on matched routes and their components.
Participating in onEnter hooks
Participating in code-splitting
Also, it just feels good to keep App decoupled from Home and let the route config decide what to render as the children. Remember, we want to build small apps inside small apps, not big ones!

Let's add a new route to index.js.

// index.js
// new imports:
// add `IndexRoute` to 'react-router' imports
import { Router, Route, hashHistory, IndexRoute } from 'react-router'
// and the Home component
import Home from './modules/Home'

// ...

render((
  <Router history={hashHistory}>
    <Route path="/" component={App}>

      {/* add it here, as a child of `/` */}
      <IndexRoute component={Home}/>

      <Route path="/repos" component={Repos}>
        <Route path="/repos/:userName/:repoName" component={Repo}/>
      </Route>
      <Route path="/about" component={About}/>
    </Route>
  </Router>
), document.getElementById('app'))
Now open http://localhost:8080 and you'll see the new component is rendered.

Notice how the IndexRoute has no path. It becomes this.props.children of the parent when no other child of the parent matches, or in other words, when the parent's route matches exactly.

Index routes can twist people's brains up sometimes. Hopefully it will sink in with a bit more time. Just think about a web server that looks for index.html when you're at /. Same idea, React Router looks for an index route if a route's path matches exactly.

09-index-links

Have you noticed in our app that we don't have any navigation to get back to rendering the Home component?

Lets add a link to / and see what happens:

// in App.js
// ...
<li><NavLink to="/">Home</NavLink></li>
// ...
Now navigate around. Notice anything weird? The link to Home is always active! As we learned earlier, parent routes are active when child routes are active. Unfortunately, / is the parent of everything.

For this link, we want it to only be active when the index route is active. There are two ways to let the router know you're linking to the "index route" so it only adds the active class (or styles) when the index route is rendered.

IndexLink

First let's use the IndexLink instead of NavLink

// App.js
import { IndexLink } from 'react-router'

// ...
<li><IndexLink to="/" activeClassName="active">Home</IndexLink></li>
Fixed! Now this link is only "active" when we're at the index route. Go ahead and click around to see.

onlyActiveOnIndex Property

We can use Link as well by passing it the onlyActiveOnIndex prop (IndexLink just wraps Link with this property for convenience).

<li><Link to="/" activeClassName="active" onlyActiveOnIndex={true}>Home</Link></li>
That's fine, but we already abstracted away having to know what the activeClassName is with Nav.

Remember, in NavLink we're passing along all of our props to Link with the {...spread} syntax, so we can actually add the prop when we render a NavLink and it will make its way down to the Link:

<li><NavLink to="/" onlyActiveOnIndex={true}>Home</NavLink></li>

10-clean-urls

Clean URLs with Browser History

The URLs in our app right now are built on a hack: the hash. It's the default because it will always work, but there's a better way.

Modern browsers let JavaScript manipulate the URL without making an http request, so we don't need to rely on the hash (#) portion of the url to do routing, but there's a catch (we'll get to it later).

Configuring Browser History

Open up index.js and import browserHistory instead of hashHistory.

// index.js
// ...
// bring in `browserHistory` instead of `hashHistory`
import { Router, Route, browserHistory, IndexRoute } from 'react-router'

render((
  <Router history={browserHistory}>
    {/* ... */}
  </Router>
), document.getElementById('app'))
Now go click around and admire your clean URLs.

Oh yeah, the catch. Click on a link and then refresh your browser. What happens?

Cannot GET /repos
Configuring Your Server

Your server needs to deliver your app no matter what URL comes in, because your app, in the browser, is manipulating the URL. Our current server doesn't know how to handle the URL.

The Webpack Dev Server has an option to enable this. Open up package.json and add --history-api-fallback.

    "start": "webpack-dev-server --inline --content-base . --history-api-fallback"
We also need to change our relative paths to absolute paths in index.html since the URLs will be at deep paths and the app, if it starts at a deep path, won't be able to find the files.

<!-- index.html -->
<!-- index.css -> /index.css -->
<link rel="stylesheet" href="/index.css">

<!-- bundle.js -> /bundle.js -->
<script src="/bundle.js"></script>
Stop your server if it's running, then npm start again. Look at those clean URLs :)

11-productionish-server

None of this has anything to do with React Router, but since we're talking about web servers, we might as well take it one step closer to the real-world. We'll also need it for server rendering in the next section.

Webpack dev server is not a production server. Let's make a production server and a little environment-aware script to boot up the right server depending on the environment.

Let's install a couple modules:

npm install express if-env compression --save
First, we'll use the handy if-env in package.json. Update your scripts entry in package.json to look like this:

// package.json
"scripts": {
  "start": "if-env NODE_ENV=production && npm run start:prod || npm run start:dev",
  "start:dev": "webpack-dev-server --inline --content-base . --history-api-fallback",
  "start:prod": "webpack && node server.js"
},
In the root directly, go open up webpack.config.js and add the publicPath '/' as per below:

// webpack.config.js
  output: {
    path: 'public',
    filename: 'bundle.js',
    publicPath: '/'
  },
When you run npm start it checks if the value of our NODE_ENV environment variable is production. If yes, it runs npm run start:prod, if not, it runs npm run start:dev.

Now we're ready to create a production server with Express and add a new file at root dir. Here's a first attempt:

// server.js
var express = require('express')
var path = require('path')

var app = express()

// serve our static stuff like index.css
app.use(express.static(__dirname))

// send all requests to index.html so browserHistory in React Router works
app.get('*', function (req, res) {
  res.sendFile(path.join(__dirname, 'index.html'))
})

var PORT = process.env.PORT || 8080
app.listen(PORT, function() {
  console.log('Production Express server running at localhost:' + PORT)
})
Now run:

NODE_ENV=production npm start
# For Windows users:
# SET "NODE_ENV=production" && npm start
Congratulations! You now have a production server for this app. After clicking around, try navigating to http://localhost:8080/package.json. Whoops. Let's fix that. We're going to shuffle around a couple files and update some paths scattered across the app.

make a public directory.
Move index.html and index.css into it.
Now let's update server.js to point to the right directory for static assets:

// server.js
// ...
// add path.join here
app.use(express.static(path.join(__dirname, 'public')))

// ...
app.get('*', function (req, res) {
  // and drop 'public' in the middle of here
  res.sendFile(path.join(__dirname, 'public', 'index.html'))
})
We also need to tell webpack to build to this new directory:

// webpack.config.js
// ...
output: {
  path: 'public',
  // ...
}
And finally (!) add it to the --content-base argument to npm run start:dev script:

"start:dev": "webpack-dev-server --inline --content-base public --history-api-fallback",
If we had the time in this tutorial, we could use the WebpackDevServer API in a JavaScript file instead of the CLI in an npm script and then turn this path into config shared across all of these files. But, we're already on a tangent, so that will have to wait for another time.

Okay, now that we aren't serving up the root of our project as public files, let's add some code minification to Webpack and gzipping to express.

// webpack.config.js

// make sure to import this
var webpack = require('webpack')

module.exports = {
  // ...

  // add this handful of plugins that optimize the build
  // when we're in production
  plugins: process.env.NODE_ENV === 'production' ? [
    new webpack.optimize.DedupePlugin(),
    new webpack.optimize.OccurrenceOrderPlugin(),
    new webpack.optimize.UglifyJsPlugin()
  ] : [],

  // ...
}
And compression in express:

// server.js
// ...
var compression = require('compression')

var app = express()
// must be first!
app.use(compression())
Now go start your server in production mode:

NODE_ENV=production npm start
You'll see some UglifyJS logging and then in the browser, you can see the assets are being served with gzip compression.

到這裏,感覺做成模板,特指 package.json, webpack.conf,以後項目直接拿來用,好象還少hot這幾個功能

12-navigating

13-server-rendering

14-whats-next

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章