create-react-app创建项目
npm install -g create-react-app
create-react-app react-cli
目录结构介绍
图中红色框是我后来加的,暂时可以不考虑。
public:里面包含了我们项目中的启动页面,react比较适合单页面项目应用开发。
- favico.ico: 这是用来表示:快捷方式 小图标。详情可以访问文章
- index.html: 初始页面。
- manifest.json: wepack打包优化相关,本人没有研究过。
- 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。
browserHistoryhashHistory
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。