一. react-router-dom
简介:
React Router是一个基于 React 之上的强大路由库,它可以让你向应用中快速地添加视图和数据流,同时保持页面与URL间的同步。
router共包含以下几种:
react-router: React Router 核心
react-router-dom: 用于 DOM 绑定的 React Router
react-router-native: 用于React Native 的 React Router
react-router-redux: React Router 和 Redux的集成
react-router-config: 静态路由配置的小助手
说明:
今天我们主要说的是 react-router-dom
react-router-dom包含 HisitoryRouter BrowserRouter等
注意:使用react-router-dom时,只需要引入react-router-dom就可以了,不需要引入react-router
<BrowserRouter>
使用
import { BrowserRouter, NavLink, Switch, Redirect} from 'react-router-dom'
<BrowserRouter basename={`${path}`}>
<ul id="menu">
<NavLink activeClassName={styles.active} exact to={`/`} />
<NavLink activeClassName={styles.active} to={`/user`} />
</ul>
<div id='page-container' className={styles.container}>
<Switch>
<Route path={`/`} exact component={main} />
<Route path={`/user`} render={(props) => <User pageId="user" />} />
<Redirect to="/" />
</Switch>
</div>
</BrowserRouter>
// NavLink添加activeClassName属性之后可以让首页tab默认选中
<Switch>
<Route exact path="/app/" render={() =>
<Redirect to='/app/error'></Redirect>}></Route>
<Route path="/app/users" component={Users}></Route>
<Route path="/app/report" component={Report}></Route>
<Route path="/app/error" component={ErrorPage}></Route>
<Redirect to="/app/" />
</Switch>
说明:
1、地址栏输入 /app/user ,跳转到指定的Users组件
2、exact指定输入 /app/重定向redirect to /app/error,重定向到ErrorPage组件中
3. 如果输入错误的地址,重定向到/app/路由,跳转到ErrorPage组件中
注意:重定向分两种 1.指定重定向,2.模糊重定向
在react-router-config中重定向路由也可以以*来替代,在所有的路由无法匹配到的时候,就跳转到404页面,但此路由需要写在路由配置的最后一个。参考:github.com/ReactTraini…
const routes = [
{ path: '/',
exact: true,
restricted: false,
component: Home,
},
{
path: '/login',
restricted: false,
component: Login,
},
{
path: '/user',
restricted: true,
component: User,
},
{
path: '*', // 此路由需放在最后,作为错误处理NotFound
restricted: false,
component: NotFound,
}
]
<Switch>
只渲染出第一个与当前地址匹配的<Route> 或<Redirect>
Switch解决的问题:如果你访问/about,那么与/about相关的路径都将被渲染出来,如/about/user,因为他们对应的路由与访问的地址/about匹配。这显然不是我们想要的,我们只想渲染出第一个匹配的路由就可以了,于是<Switch>应用而生!
路由实现 - history
<header>
<a onclick="changeRoute(this)" data-path="home">首页</a>
<a onclick="changeRoute(this)" data-path="center">个人中心</a>
<a onclick="changeRoute(this)" data-path="help">帮助</a>
</header>
<section id="content"></section>
<script>
/**
Hisotory.pushState(state, title, url) 按指定的名称和URL(如果提供该参数)将数据push进回话历史栈。
state: 一个与添加的记录相关联的状态对象,主要用于popstate事件,该事件触发时,该对象会传入回调函数。也就是说,浏览器会将这个对象序列化以后保留在本地,重新载入这个页面的时候,可以拿到这个对象。如果不需要这个对象,此处可以填为null
title: 新页面的标题。但是,现在所有浏览器都忽视这个参数,所以这里可以填空字符串。
url: 新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个地址。
*/
function changeRoute (route) {
let path = route.dataset.path
changePage(path)
history.pushState({content: path}, null, path)
}
/**
当活动历史记录条目更改时,将触发popstate事件。history.pushState()的调用创建的,或者受到对 history.replaceState()的调用的影响,popstate事件的state属性包含历史条目的状态对象的副本。
history.pushState() 或者 history.replaceState()不会触发popstate事件。只有在做出浏览器动作时,才会触发该事件,如用户点击浏览器的回退按钮 或者在js代码中调用history.back()
*/
window.addEventListener('popstate', (e) => {
let content = e.state && e.state.content
changePage(content)
})
function changePage (pageContent) {
let content = document.getElementById('content')
content.innerText = pageContent
}
</script>
-
BrowerRouter
<BrowerRouter>使用HTML5的historyAPI (pushState、replacestatepopstate事件)来同步URL和UI
<BrowerRouter basename={optionalString} forceRefresh={optionlBool} getUserConfirmation={optionlFunc} keyLength={optionlNumber}>
<App/>
</BrowerRouter>
属性介绍
basename: string:基准URL.适用于当应用置于服务器上子目录的情况下。
forceRefresh: bool: 当设为true时,每次跳转都会刷新页面。一般只在浏览器不支持HTML history API的情况下使用。
getUserConfirmation: func: 可以传入一个函数,用来确定导航前的用户确认行为,默认使用window.confirm,如下:
keyLength: number: 设置location.key的长度,默认为6位
children: node:子元素里只能渲染单一的元素
-
HashRouter
使用URL中的hash部分(即
window.location.hash)来保持UI和URL同步注意:HashHistory不支持
location.key或location.state,所以任何需要这两个属性的代码或者插件将不能起作用。由于HashHisitory这种技术的主要目的是支持老式浏览器,所以官方更推荐的做法是对服务器进行配置,然后采用<BrowerRouter>代替。 -
MemoryRouter
在内存中记录history的路由组件(这种路由不会读取或者写入地址栏),适合用于测试或者非浏览器环境(如
ReactNative)
导航跳转
- Link
为应用提供声明式、可访问的导航的一个组件:
import { Link } from 'react-router-dom'
<Link to="/about">About</Link>
参数: to: string 要跳转到的 pathname 或者 location
to: object
用法:
<Link to={{
pathname: '/course',
search: '?sort=name',
hash: '#the-hash',
state: { fromDashboard: true }
}} />
replace: bool 当这个值为 true 时,会替换掉history栈里当前的history,而不是在栈里新增一条history,如:
<Link to="/courses" replace>
- NavLink
<Link>组件的特殊版本,当前URL匹配时,会在元素上增加样式相关的属性activeClassName,从而达到高亮标记的效果。
import { NavLink } from 'react-router-dom'
<NavLink to="/about">About</NavLink>
参数:
activeClassName: string
规定处于激活状态时的类名,默认值是active,会自动和className属性合并:
<NavLink to="/faq" activeClassName="selected">FAQs</NavLink>
activeStyle: object
规定激活状态下应用到元素上的样式:
<NavLink to="/faq" activeStyle={{
fontWeight: 'bold',
color: 'red'
}}>FAQs</NavLink>
exact: bool设为true时,只有当location完全匹配的时候才会添加类名或者样式:
与Switch的区别
<NavLink exact to="/profile">Profile</NavLink>
strict: bool:当设为 true时,location中pathname末尾的/就会在匹配URL的时候被考虑:
就是说有/和没有/的结果完全不同
<NavLink strict to="/events/">Events</NavLink>
isActive: func:用来为link添加判断激活状态额外逻辑的函数,如:
isActive可以通过逻辑来控制
// 只有当事件ID为奇数时才可能为激活状态
const oddEvent = (match, location) => {
if (!match) {
return false
}
const eventID = parseInt(match.params.eventID)
return !isNaN(eventID) && eventID % 2 === 1
}
<NavLink to="/events/263" isActive={oddEvent}>Event 263</NavLink>
location: object
isActive函数里用来比较用的值(即传入的第二个参数),通常情况下取值为当前浏览器的URL.如果要和不同的location比较,就可以用这个属性来传值。
<NavLink activeClassName={styles.active} exact to={`/`} />
二. Router、Route与Switch 1、Router 通用的基础路由组件。通常在应用里会采用派生的路由代替,有:
<BrowserRouter>
<HashRouter>
<MemoryRouter>
<NativeRouter>
<StaticRouter>
最常用的直接采用<Router>的场景是用状态管理库(Redux、Mobx)来同步一个自定义的history。不过应当注意的是:这不是说必须结合状态管理库使用ReactRouter,只是在深度集成的时候需要用到:
import { Router } from 'react-router'
import createBrowserHistory from 'history/createBrowserHistory'
const history = createBrowserHistory()
<Router history={history}>
<App />
</Router>
参数:
history: object 用来导航用的history对象
2、Route
组件也许是ReactRouter里最需要好好理解和使用的最重要的一个组件。它的基本职责是在location匹配路由的path参数值时渲染特定的UI:
import { BrowserRouter as Router, Route } from 'react-router-dom'
<Router>
<div>
<Route exact path="/" component={Home} />
<Route path="/news" component={NewsFeed} />
</div>
</Router>
当location是/时,UI将会渲染为:
<div>
<Home />
<!-- react-empty: 2 -->
</div>
而如果当UI是/news时,UI将会渲染为:
<div>
<!-- react-empty: 1 -->
<NewsFeed />
</div>
react-empty注释,只是React的null渲染的实现。但是这种做法是有益的,从技术上而言一个Route应该总是被渲染出来即使结果是渲染null,而只要应用的location和Route的path匹配,那么对应的组件就会得到渲染。
2-1、路由渲染方法
渲染一个路由有三种方式:
<Route component>
<Route render>
<Route children>
每一种方式在不同的情景下都是有用的,但是在<Route>里只能同时用一个属性,不过大部分情况下用的是component
注意: 由于优先级上:component > render > children,所以在<Route>里最多只能选用一种。
首先需要知道的是,这三种方式都会得到match、location、history这三个路由属性。
说明:
component方式,表明location匹配时要渲染的React组件,组件可以使用路由属性来进行渲染:
<Route path="/user/:username" component={User} />
const User = ({ match }) => {
return <h1>Hello, {match.params.username}!</h1>
}
当使用这种方式时,路由系统会使用React.createElement来从给定的组件里创建一个新的React元素。这意味着如果传给component属性的是一个内联的函数,那么每次渲染时就都会 创建一个新的组件。这就会导致现有的组件卸载,然后挂载新的组件。而非更新已有的组件。所以要使用内联函数来进行内联渲染时,可以使用render方式或者children方式
render: func方式,这种方式将方便于内联渲染,并且不会有上述的重新挂载问题。我们可以传入一个函数,那么当location匹配的时候这个函数就会被调用,从而不会进行新的React元素创建过程。render属性接收和component属性一致的路由属性:
<Route path="/home" render={() => <div>Home</div>} />
const FadingRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
<FadeIn>
<Component {...props} />
</FadeIn>
)} />
)
<FadingRoute path="/cool" component={Something} />
children: func方式,有时候,无论path是否成功匹配都需要进行渲染。那么这种情况下,可以使用children属性方式,这种方式无论匹配是否成功都会调用对应的函数。
而children渲染属性接收和component、render方式一样的属性,不过当路由不匹配的时候,match的值是null。所以采用这种方式的好处是,我们可以灵活地动态调整UI,无论路由是否匹配。举例如:
路由匹配时添加激活状态的类名:
<ul>
<ListItemLink to="/somewhere" />
<ListItemLink to="/somewhere-else" />
</ul>
const ListItemLink = ({to, ...rest}) => (
<Route path={to} children={({ match }) => (
<li className={match ? 'active' : ''}>
<Link to={to} {...rest} />
</li>
)}/>
)
对于动画而言,这种方式也是有用的:
<Route children={({ match, ...rest }) => (
// 因为动画会一直渲染,所以可以使用生命周期来让子元素动画进出
<Animate>
{match && <Something {...rest}/>}
</Animate>
)}
3、Switch
渲染子元素里第一个匹配的<Route>或者<Redirect>,那么,和只使用一堆<Route>不同的是什么?
<Switch>独一无二的是它只渲染一个路由。而反过来,每个匹配location的<Route>都会被得到渲染,参考如下代码:
<Route path="/about" component={About} />
<Route path="/:user" component={User} />
<Route component={NoMatch} />
如果URL是/about,那么<About>、<User>和<NoMatch>都会渲染,因为它们的path都得到了匹配。而这种设计是有意的,因为这使得我们可以用多种方式来用<Route>组成我们的应用,如侧边栏、面包屑、引导Tab等。
不过有时候,我们只想要拾取一个<Route>来渲染,就比如URL处于/about时就不希望它匹配/:user(或者展示404页面),如:
import { Switch, Route } from 'react-router'
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/:user" component={User} />
<Route component={NoMatch} />
</Switch>
现在,如果URL是/about,那么<Switch>就会开始寻找匹配的<Route>。<Route path="/about"/>将会匹配,从而<Switch>就会停止匹配然后渲染出<About>组件。相似地,如果URL处于/michael,那么<User>组件就会渲染。
对于动画过渡而言,这也是一种有用的方式,因为匹配的<Route>需要渲染在先前相同的位置:
<Fade>
<Switch>
{/* 这里只会有一个子节点 */}
<Route/>
<Route/>
</Switch>
</Fade>
<Fade>
<Route/>
<Route/>
{/*
这里一直都会有两个子节点,尽管其中一个可能为null,
这会使得过渡效果起效果有点难处理
*/}
</Fade>
参数说明:
location: object 和之前其他地方里含义一致
children: node
<Switch>组件的子元素都应该是<Router>或者<Redirect>,并且只有匹配当前URL的第一个子元素会被渲染。其中,<Route>元素使用path属性来进行匹配,而<Redirect>使用from属性进行匹配。而不带path属性的<Route>元素或者不带from属性的<Redirect>元素会总是匹配当前location
所以当在<Switch>里使用<Redirect>组件时,它可以使用和<Route>一样的匹配属性:path、exact、strict,而from只是path属性的别名。
当给<Switch>传入location属性时,会覆盖当前匹配的子元素中的location
<Switch>
<Route exact path="/" component={Home} />
<Route path="/users" component={Users} />
<Redirect from="/accounts" to="/users" />
<Route component={NoMatch} />
</Switch>
- redux
异步获取数据
- 高阶组件
- 程序设计
在接口的设计上应体现出数据与业务的相分离。降低耦合性。在代码的设计上也要尽量降低重复性代码。在写代码之前要清楚接口的数据结构,设计好代码结构,尽量减少在写代码时候频繁修改代码。
- dangerouslySetInnerHTML
7.API
react中最重要的三个API: createElement 、render、 component
component(react中一切皆组件)中包含setState
virtual Dom
jsx表面看是HTML标签,实际上是下面这套内容
React.createElement(
"div",
null,
"hello ",
this.props.name,
" ,I am",
{2 + 2},
"years old"
)
是jsx的babel做的编译,jsx表面是html,实际上是js
React.createElement返回的数据
JSX本质上就是转换为React.createElement在react内部构建虚拟dom,最终渲染出页面
React.createElement是用来创建虚拟DOM的
React.createElement是一个递归调用的过程,也就是说dom中嵌套dom,会嵌套相应个数的React.createElement
学习一个库的最好方法,先开发一个小应用,然后尝试理解源码。
参考链接:ReactRouter4学习笔记
二、分析执行流程
<div>
<A />
<B>
<C />
<D />
</B>
</div>
请说出componentWillUpdate和componentDidUpdate中A B C D四个组件的执行顺序。
- componentWillMount
A B C D
- componentDidMount
A C D B
三、PureComponent
原理:当组件更新时,如果组件的props和state都没发生改变,render方法都不会触发,省去Virtual DOM的生成和对比过程,达到提升性能的目的。React自动帮我们做了一层浅比较。 PureComponent真正起作用的,只是在一些纯展示组件上,复杂组件使用的话shallowEqual那一关基本就过不了。
shouldComponentUpdate
不可变数据的力量,返回新对象,而不是修改老对象。
大部分情况下,可以使用 React.PureComponent 来代替手写 shouldComponentUpdate。
四、setState
setState只在合成事件和钩子函数中是"异步"的,在原生事件和setTimeout中都是同步的。setState的"异步"并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,也可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。setState的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout中不会批量更新,在“异步”中如果对一个值进行多次setState,setState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时会对其进行合并批量更新。
五、readux-thunk中间件原理
redux-thunk为什么能让action发送函数 redux-thunk是对dispatch方法做一个升级,之前这个dispatch方法只能接收一个对象,升级之后,就可以接收函数了。根据参数的不同执行不同的事情,如果是对象就直接传递给store,如果是函数就先执行函数,在调用dispatch。原理就是对store的dispatch方法做了一个升级。
四、Props.childern
六、componentDidCatch处理错误异常
componentDidCatch
参考文档:
最佳实践:
demo1: codepen.io/sgroff04/pe…
demo2: codepen.io/gaearon/pen…
七、react-router原理之Link跳转
Link最终被转换成<a>标签的形式,那么和代码里直接写成a标签有什么区别呢?最大的区别表象体现在是否有主文档请求,<a>标签一定会出现,而Link则通常不出现(除非特殊配置)
Link不仅是<a>标签,Link在最后渲染的时候创建了<a>标签,同时添加了一个onClick的监听事件,onClick事件处理函数中做了两件事:
- 如果
Link上定义了onClick方法,则执行该方法 - 判定是否应该阻止
a标签的默认跳转
- 如果阻止的话,则根据
replace的props值决定执行history.push,还是执行history.replace。Link只负责触发url变更,Route只负责根据url渲染组件,history的作用是执行url变更,并同时通知Route重新渲染。 - 如果不阻止的话,则其实与直接
<a>标签的写法类似了,当点击操作触发时会产生主文档的请求。
Link在同时满足下列四个条件时就会阻止默认行为:
Link的onClick回调中不包含event.preventDefault()代码- 点击按键是左键
Link的props未定义target- 点击操作触发时没有功能键(
ctrl、shift等)被按下
!event.defaultPrevented && event.button === 0 && !this.props.target && !isModifiedEvent(event)
特殊的Link,react-router中除了Link还有一个NavLink,NavLink是一种特殊的Link,它的特殊体现在当与url匹配时生成的标签上会带有一些样式信息(通过activeClassName和activeStyle定义样式)。