React总结

711 阅读16分钟

一. 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

pushState()

popstate

路由实现 - 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的history API (pushStatereplacestate popstate事件)来同步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.keylocation.state,所以任何需要这两个属性的代码或者插件将不能起作用。由于HashHisitory这种技术的主要目的是支持老式浏览器,所以官方更推荐的做法是对服务器进行配置,然后采用<BrowerRouter>代替。

  • MemoryRouter

    在内存中记录history的路由组件(这种路由不会读取或者写入地址栏),适合用于测试或者非浏览器环境(如ReactNative

导航跳转

  1. 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>
  1. 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>
  1. redux
异步获取数据
  1. 高阶组件

  1. 程序设计
在接口的设计上应体现出数据与业务的相分离。降低耦合性。在代码的设计上也要尽量降低重复性代码。在写代码之前要清楚接口的数据结构,设计好代码结构,尽量减少在写代码时候频繁修改代码。
  1. 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>

请说出componentWillUpdatecomponentDidUpdateA B C D四个组件的执行顺序。

  1. componentWillMount
A B C D
  1. componentDidMount
A C D B

三、PureComponent

原理:当组件更新时,如果组件的props和state都没发生改变,render方法都不会触发,省去Virtual DOM的生成和对比过程,达到提升性能的目的。React自动帮我们做了一层浅比较。 PureComponent真正起作用的,只是在一些纯展示组件上,复杂组件使用的话shallowEqual那一关基本就过不了。

shouldComponentUpdate

React性能优化

不可变数据的力量,返回新对象,而不是修改老对象。

大部分情况下,可以使用 React.PureComponent 来代替手写 shouldComponentUpdate。

四、setState

  1. setState只在合成事件和钩子函数中是"异步"的,在原生事件和setTimeout中都是同步的。
  2. setState的"异步"并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,也可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
  3. setState的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout中不会批量更新,在“异步”中如果对一个值进行多次setState,setState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时会对其进行合并批量更新。

五、readux-thunk中间件原理

redux-thunk为什么能让action发送函数 redux-thunk是对dispatch方法做一个升级,之前这个dispatch方法只能接收一个对象,升级之后,就可以接收函数了。根据参数的不同执行不同的事情,如果是对象就直接传递给store,如果是函数就先执行函数,在调用dispatch。原理就是对store的dispatch方法做了一个升级。

四、Props.childern

六、componentDidCatch处理错误异常

componentDidCatch

参考文档:

  1. zh-hans.reactjs.org/docs/error-…
  2. www.zcfy.cc/article/2-m…
  3. juejin.cn/post/684490…

最佳实践:

demo1: codepen.io/sgroff04/pe…

demo2: codepen.io/gaearon/pen…

七、react-router原理之Link跳转

Link最终被转换成<a>标签的形式,那么和代码里直接写成a标签有什么区别呢?最大的区别表象体现在是否有主文档请求,<a>标签一定会出现,而Link则通常不出现(除非特殊配置)

Link不仅是<a>标签,Link在最后渲染的时候创建了<a>标签,同时添加了一个onClick的监听事件,onClick事件处理函数中做了两件事:

  1. 如果Link上定义了onClick方法,则执行该方法
  2. 判定是否应该阻止a标签的默认跳转
  • 如果阻止的话,则根据replaceprops值决定执行history.push,还是执行history.replaceLink只负责触发url变更,Route只负责根据url渲染组件,history的作用是执行url变更,并同时通知Route重新渲染。
  • 如果不阻止的话,则其实与直接<a>标签的写法类似了,当点击操作触发时会产生主文档的请求。

Link在同时满足下列四个条件时就会阻止默认行为:

  • LinkonClick回调中不包含event.preventDefault()代码
  • 点击按键是左键
  • Linkprops未定义target
  • 点击操作触发时没有功能键(ctrlshift等)被按下

!event.defaultPrevented && event.button === 0 && !this.props.target && !isModifiedEvent(event)

特殊的Link,react-router中除了Link还有一个NavLink,NavLink是一种特殊的Link,它的特殊体现在当与url匹配时生成的标签上会带有一些样式信息(通过activeClassName和activeStyle定义样式)。