npm install -g create-react-app
create-react-app react-cli
图中红色框是我后来加的,暂时可以不考虑。
至于为什么初始加载的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变化。(这一点相信不难理解,可以访问前一篇文章)。
//index.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render( , 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 (
)
}
}
export default App;
// containers/about.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class About extends Component {
render() {
return (
About
);
}
}
export default About;
// containers/home.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class Home extends Component {
render() {
return (
Home
);
}
}
export default Home;
// containers/inbox.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class Inbox extends Component {
render() {
return (
Inbox
);
}
}
export default Inbox;
源码地址: https://github.com/rodchen-king/react/tree/v0.1/react-cli
React Router 是一个基于 React 之上的强大路由库,它可以让你向应用中快速地添加视图和数据流,同时保持页面与 URL 间的同步。
npm install [email protected] --save
此处说明一下:因为当前的react版本是16.3.2,因为react16的不能兼容 react-router 2/3, 如果要用 react-router2/3,需将react降级到15,所以我这里用的[email protected],[email protected]。
举例:
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 (
App
- About
- Inbox
{this.props.children}
)
}
})
const About = React.createClass({
render() {
return About
}
})
const Inbox = React.createClass({
render() {
return (
Inbox
{this.props.children || "Welcome to your Inbox"}
)
}
})
const Message = React.createClass({
render() {
return Message {this.props.params.id}
}
})
ReactDOM.render((
), document.getElementById('root'))
ReactDOM.render((
), document.getElementById('root'));
React Router 的重要组件。它能保持UI和URL的同步,其内部包含多个路由。
children (required)
一个或多个的 Route 或 PlainRoute。当 history 改变时,
routes
children 的别名。
const routes = (
);
ReactDOM.render((
), document.getElementById('root'))
对于children和routes之间的区别没有研究过,
path
这个例子中中,/App/about可以访问到About组件,但是/App/inbox访问不到InBox组件。因为路由path以/开头。
component
当匹配到URL时,单个的组件会被渲染。它可以被父route 组件的this.props.children渲染。这个知识点很重要,因为父子组件通过路由渲染的过程中,是需要一个地方渲染对应组件内容。所以每个父组件都会设置this.props.children来划定一个渲染子组件的区域。
const App = React.createClass({
render() {
return (
App
- About
- Inbox
{this.props.children}
)
}
})
components
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 (
App
{main}
- groups
- users
{sidebar}
)
}
})
const Groups = React.createClass({
render() {
return Groups
}
})
const GroupsSidebar = React.createClass({
render() {
return (
GroupsSidebar
)
}
})
const Users = React.createClass({
render() {
return Users
}
})
const UsersSidebar = React.createClass({
render() {
return (
UsersSidebar
{this.props.children}
)
}
})
const Profile = React.createClass({
render() {
return (
Profile {this.props.params.id}
)
}
})
const routes = (
)
ReactDOM.render((
), document.getElementById('root'))
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 (
App
- About
- Inbox
{this.props.children}
)
}
})
const About = React.createClass({
render() {
return About
}
})
const Inbox = React.createClass({
render() {
return (
Inbox
{this.props.children || "Welcome to your Inbox"}
)
}
})
const Message = React.createClass({
render() {
return Message {this.props.params.id}
}
})
const Dashboard = React.createClass({
render() {
return Hello!
}
})
const EnterHook = function(RouterState, RedirectFunction, callback) {
console.log("进入about");
}
const LeaveHook = function(RouterState) {
console.log("离开about");
}
const routes = (
);
ReactDOM.render((
), document.getElementById('root'))
const routes = [
{ path: '/',
component: App,
indexRoute: { component: Dashboard },
childRoutes: [
{ path: 'about', component: About },
{ path: 'inbox',
component: Inbox,
childRoutes: [
{ path: '/messages/:id', component: Message }
]
}
]
}
]
// matches /hello/michael and /hello/ryan
// matches /hello, /hello/michael, and /hello/ryan
// matches /files/hello.jpg and /files/hello.html
// matches /files/hello.jpg and /files/path/to/file.jpg
- About
About
- Inbox
React Router 是建立在 history 之上的。 简而言之,一个 history 知道如何去监听浏览器地址栏的变化, 并解析这个 URL 转化为 location 对象, 然后 router 使用它匹配到路由,最后正确地渲染对应的组件。
常用的 history 有三种形式, 但是你也可以使用 React Router 实现自定义的 history。
browserHistorycreateMemoryHistory
在我没有使用任何的方式的情况下,路由形如下面:
http://localhost:3000/inbox#/?_k=iawvme
然后修改代码如下,加上history={browserHistory}
路由变化就是:http://localhost:3000/inbox
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 About
}
})
组件的生命周期内我们可以通过this.props.params获取到。
写在最后:react 路由知识还有许多东西需要探索,但是可以先了解基本知识再去拓展,对于版本的升级可以上网简单搜索一下,应该就可以解决了。最后吐槽一下react的生态圈真的不如angular。