前端框架,快速开发页面,函数式编程,与后端api快速搭建
JSX语法
JSX是React中为了便于编写而创建的语法,
jsx也就是js中写xml的意思
组件
函数 function
参数传递
外部...perpson
表示接收外部数组内的所有东西,称为属性扩散
构造函数使用组件外部参数时,必须使用在构造函数的参数列表中定义props属性接收,props是形参,只能读取,不能更改外部数据
类
使用类时默认类中默认有constructor构造器
类继承
关键字 extends
继承类中的constructor必须在第一行有super(),表示对父类中的constructor 的引用
在constructor中引用外部数据也必须用形参
1 | render (){ |
render中直接用this.props使用外界传递的参数
导出函数组件和类组件
export default class
类组件是有状态组件,有自己的state和生命周期函数,函数组件是无状态组件
jsx return与createelement api
jsx元素就是调用react.createElement的语法糖。一般来说,使用了jsx就不需要使用createElement和createFactory这两个api
创建React项目
安装包
1 | npm i react-router-dom --save |
创建react项目
1 | npm install -g create-react-app |
使用create-react-app命令创建项目
1 | create-react-app [项目名] |
启动项目
1 | npm start |
为什么会有react和react-dom两个包?什么时候替换react-dom
react在v0.14之前是没有React DOM的,所有的功能都包含在React里。从v0.14开始,react被拆分成react和reactDom,因为有了react Native,react只负责web和mobile的通用核心部分,负责DOM操作的分到ReactDOM中,负责Mobile的包含在react-native中
react-dom只做和浏览器或者DOM相关的操作。如ReactDOM.render,reactDom.findDomNode(),如果是服务端渲染可以reactDOM.renderToString()。React不仅能通过ReactDom和web页面打交道,还能在服务端SSR,移动端React Native和桌面端Electron
web端代码
1 | import React from 'react' |
React-native
1 | import React from 'react' |
React包api:
创建React组件:React.Component()、React.PureComponent()
创建可包装函数:React.memo():如果你的组件在相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo
中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。
React.memo
仅检查 props 变更。如果函数组件被 React.memo
包裹,且其实现中拥有 useState
,useReducer
或 useContext
的 Hook,当 state 或 context 发生变化时,它仍会重新渲染。
操作元素的api,进行转换元素操作:
cloneElement()
: 以 element
元素为样板克隆并返回新的 React 元素。config
中应包含新的 props,key
或 ref
。返回元素的 props 是将新的 props 与原始元素的 props 浅层合并后的结果。新的子元素将取代现有的子元素,如果在 config
中未出现 key
或 ref
,那么原始元素的 key
和 ref
将被保留。
createElement()
: 创建并返回指定类型的新 React 元素。其中的类型参数既可以是标签名字符串(如 'div'
或 'span'
),也可以是 React 组件 类型 (class 组件或函数组件),或是 React fragment 类型。
isValidElement()
:验证对象是否为 React 元素,返回值为 true
或 false
。
React.children()
createFactory()
: 已废弃,建议使用JSX 或直接调用 React.createElement()
来替代它。
Fragments减少不必要嵌套的组件:
React.Fragments: React.Fragment
组件能够在不额外创建 DOM 元素的情况下,让 render()
方法中返回多个元素。
Refs:
React.createRef: React.createRef
创建一个能够通过 ref 属性附加到 React 元素的 ref。
React.forwardRef: React.forwardRef
会创建一个React组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中。
Suspense:
suspense使组件可以等待某些操作结束后再进行渲染。通过react.lazy动态加载组件
react.suspense: React.Suspense
可以指定加载指示器(loading indicator),以防其组件树中的某些子组件尚未具备渲染条件。
react.lazy: React.lazy()
允许你定义一个动态加载的组件。这有助于缩减 bundle 的体积,并延迟加载在初次渲染时未用到的组件。
React-dom api
render: ReactDOM.render(element, container[, callback])
在提供的
container
里渲染一个 React 元素,并返回对该组件的引用(或者针对无状态组件返回null
)。如果 React 元素之前已经在
container
里渲染过,这将会对其执行更新操作,并仅会在必要时改变 DOM 以映射最新的 React 元素。如果提供了可选的回调函数,该回调将在组件被渲染或更新之后被执行。
hydrate: ReactDOM.hydrate(element, container[, callback])
与 render()
相同,但它用于在 ReactDOMServer
渲染的容器中对 HTML 的内容进行 hydrate 操作。React 会尝试在已有标记上绑定事件监听器。
React 希望服务端与客户端渲染的内容完全一致。React 可以弥补文本内容的差异,但是你需要将不匹配的地方作为 bug 进行修复。在开发者模式下,React 会对 hydration 操作过程中的不匹配进行警告。但并不能保证在不匹配的情况下,修补属性的差异。由于性能的关系,这一点非常重要,因为大多是应用中不匹配的情况很少见,并且验证所有标记的成本非常昂贵。
如果单个元素的属性或者文本内容,在服务端和客户端之间有无法避免差异(比如:时间戳),则可以为元素添加 suppressHydrationWarning={true}
来消除警告。这种方式只在一级深度上有效,应只作为一种应急方案(escape hatch)。请不要过度使用!除非它是文本内容,否则 React 仍不会尝试修补差异,因此在未来的更新之前,仍会保持不一致。
如果你执意要在服务端与客户端渲染不同内容,你可以采用双重(two-pass)渲染。在客户端渲染不同内容的组件可以读取类似于 this.state.isClient
的 state 变量,你可以在 componentDidMount()
里将它设置为 true
。这种方式在初始渲染过程中会与服务端渲染相同的内容,从而避免不匹配的情况出现,但在 hydration 操作之后,会同步进行额外的渲染操作。注意,因为进行了两次渲染,这种方式会使得组件渲染变慢,请小心使用。
记得保证弱网环境下的用户体验。JavaScript 代码的加载要比最初的 HTML 渲染晚的多。因此如果你只在客户端渲染不同的内容,其转换可能会不稳定。但是,如果执行顺利,那么在服务端负责渲染的 shell 会对渲染提供帮助,并且只显示客户端上额外的小组件。
- findDOMNode: ReactDOM.findDOMNode(component)
findDOMNode
是一个访问底层 DOM 节点的应急方案(escape hatch)。在大多数情况下,不推荐使用该方法,因为它会破坏组件的抽象结构。
- unmountComponentAtNode: ReactDOM.unmountComponentAtNode(container)
从 DOM 中卸载组件,会将其事件处理器(event handlers)和 state 一并清除。如果指定容器上没有对应已挂载的组件,这个函数什么也不会做。如果组件被移除将会返回 true
,如果没有组件可被移除将会返回 false
。
- createPortal: ReactDOM.createPortal(child, container)
创建 portal。Portal 将提供一种将子节点渲染到 DOM 节点中的方式,该节点存在于 DOM 组件的层次结构之外。
React-dom-server
ReactDOMServer
对象允许你将组件渲染成静态标记。通常,它被使用在 Node 服务端上,也就是SSR
api
1 | // ES modules |
1 | import ReactDOMServer from 'react-dom/server'; |
将 React 元素渲染为初始 HTML。React 将返回一个 HTML 字符串。你可以使用此方法在服务端生成 HTML,并在首次请求时将标记下发,以加快页面加载速度,并允许搜索引擎爬取你的页面以达到 SEO 优化的目的。
如果你在已有服务端渲染标记的节点上调用 ReactDOM.hydrate()
方法,React 将会保留该节点且只进行事件处理绑定,从而让你有一个非常高性能的首次加载体验。
1 | ReactDOMServer.renderToStaticMarkup(element) |
此方法与 renderToString
相似,但此方法不会在 React 内部创建的额外 DOM 属性,例如 data-reactroot
。如果你希望把 React 当作静态页面生成器来使用,此方法会非常有用,因为去除额外的属性可以节省一些字节。
如果你计划在前端使用 React 以使得标记可交互,请不要使用此方法。你可以在服务端上使用 renderToString
或在前端上使用 ReactDOM.hydrate()
来代替此方法。
1 | ReactDOMServer.renderToNodeStream(element) |
将一个 React 元素渲染成其初始 HTML。返回一个可输出 HTML 字符串的可读流。通过可读流输出的 HTML 完全等同于 ReactDOMServer.renderToString
返回的 HTML。你可以使用本方法在服务器上生成 HTML,并在初始请求时将标记下发,以加快页面加载速度,并允许搜索引擎抓取你的页面以达到 SEO 优化的目的。
如果你在已有服务端渲染标记的节点上调用 ReactDOM.hydrate()
方法,React 将会保留该节点且只进行事件处理绑定,从而让你有一个非常高性能的首次加载体验。
1 | ReactDOMServer.renderToStaticNodeStream(element) |
此方法与 renderToNodeStream
相似,但此方法不会在 React 内部创建的额外 DOM 属性,例如 data-reactroot
。如果你希望把 React 当作静态页面生成器来使用,此方法会非常有用,因为去除额外的属性可以节省一些字节。
通过可读流输出的 HTML,完全等同于 ReactDOMServer.renderToStaticMarkup
返回的 HTML。
如果你计划在前端使用 React 以使得标记可交互,请不要使用此方法。你可以在服务端上使用 renderToNodeStream
或在前端上使用 ReactDOM.hydrate()
来代替此方法。
路由react-router-dom
安装包
1 | npm i --save react-router-dom |
react-router与react-router-dom是两个包,前者提供react的核心api,如Router、Route、Switch等,后者包含前者的依赖,并且提供BrowserRoute、Route、Link等api,可以通过dom操作触发事件控制路由。
一般使用后者
导入包,新建Router.js
1 | import React from 'react'; |
Home.jsx
1 | import React,{Component} from 'react'; |
About.jsx
1 | import React,{Component} from 'react'; |
1 | <HashRouter> |
在app.js中引入
1 | import Router from './Router' |
NavLink是一个特殊版本的Link,可以使用activeClassName来设置Link被选中时被附加的class,使用activeStyl
来配置被选中时应用的样式。
路由匹配
Switch
1 | import { HashRouter,Route,Switch } from 'react-router-dom' |
嵌套路由
router.js
1 | import Other from './components/Other' |
重定向
1 | import {Redirect} from "react-router-dom" |
IndexRedirect
组件用于访问根路由的时候,将用户重定向到某个子组件。
其他API
RouteComponentProps
Prompt
跳转之前提示
1 | /** |
withRouter
默认情况下必须是经过路由匹配渲染的组件才存在this.props,才拥有路由参数,才能使用编程式导航的写法,执行this.props.history.push(‘/detail’)跳转到对应路由的页面
然而不是所有组件都直接与路由相连(通过路由跳转到此组件)的,当这些组件需要路由参数时,使用withRouter就可以给此组件传入路由参数,此时就可以使用this.props
设置withRouter很简单只需要两步:(1)引入 (2)将App组件 withRouter() 一下
比如app.js这个组件,一般是首页,不是通过路由跳转过来的,而是直接从浏览器中输入地址打开的,如果不使用withRouter此组件的this.props为空,没法执行props中的history、location、match等方法。
1 | import React,{Component} from 'react' |
match
match是一个匹配路径参数的对象,它有一个属性params,里面的内容就是路径参数,除常用的params属性外,它还有url、path、isExact属性。
history对象
监听路由变化
根据history对象判断路由变化作进一步操作
1 | class Header extends Component { |
nginx与webpack配置
单页应用(SPA)一般只有一个index.html, 导航的跳转都是基于HTML5 History API,当用户在越过index.html 页面直接访问这个地址或是通过浏览器的刷新按钮重新获取时,就会出现404问题;
在webpack.config.js
里面增加配置:
1 | devServer { |
webpack
里面的 historyApiFallback
使用的是connect-history-api-fallback
1 | location /react { |
autoindex on; 开启这个,输入到/react 会直接定向到index.html;
try_files 主要解决的是,如果在一些目录下找不到 index.html, 会最终有一个保底资源的路径就是 /react/index.html;
State与生命周期
与props不同,state是组件内的私有数据,且完全受控于当前组件。
对于上述两种组件,function函数式定义的组件不支持state,所以要使用state需要把组件定义为class式组件
使用时在组件内添加构造函数constuctor
1 | class Clock extends React.Component { |
修改组件中的state数据时,使用setState
1 | this.setState({comment:'hello'}); |
调用setState方法后react会自动更新state
当你需要更新多个state数据时,可以使用setState独立更新,但是如果state之间互相依赖,会涉及到异步更新的问题,此时setState需要接收一个函数而不是简单的对象,
1 | this.setState(function(state,props){ |
组件可以把state作为props向下传递到子组件中
1 | <FormattedData date={this.state.date} /> |
在React中,组件是有状态组件还是无状态组件是由组件的细节决定的,它可能会随着时间的推移而改变。有状态无状态只针对于组件本身,对外相同,因此可以在有状态组件中调用无状态组件,反之亦然。
1 | import React, { Component } from 'react' |
生命周期
生命周期函数
旧版生命周期
ComponentWillMount(){}在新版生命周期中被弃用
ComponentDidMount(){}
ComponentWillReceiveProps(Props)在新版生命周期中被弃用
ShouldComponentUpdate(Props){}
ComponentWillUpdate(Props){}在新版生命周期中被弃用
ComponentDidUpdate(Props){}
ComponentWillUnmount(Props){}
当初次加载DOM时,下列生命周期函数会被依次调用:
constructor()
:初始化 state 或进行方法绑定static getDerivedStateFromProps()
render()
componentDidMount()
:会在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方。
DOM更新时,会依次调用生命周期函数:
static getDerivedStateFromProps()
:getDerivedStateFromProps
会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回null
则不更新任何内容。shouldComponentUpdate()
:根据shouldComponentUpdate()
的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。默认行为是 state 每次发生变化组件都会重新渲染。大部分情况下,你应该遵循默认行为。render()
getSnapshotBeforeUpdate()
:在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给componentDidUpdate()
componentDidUpdate()
:会在更新后会被立即调用。首次渲染不会执行此方法。当组件更新后,可以在此处对 DOM 进行操作。如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求。
componentWillUnmount()
会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount()
中创建的订阅等。
错误处理生命周期函数
新版生命周期
static getDerivedStateFromProps
static getDerivedStateFromProps(nextProps,prevState)
:接收父组件传递过来的 props
和组件之前的状态,返回一个对象来更新 state
或者返回 null
来表示接收到的 props
没有变化,不需要更新 state
该生命周期钩子的作用: 将父组件传递过来的 props
映射 到子组件的 state
上面,这样组件内部就不用再通过 this.props.xxx
获取属性值了,统一通过 this.state.xxx
获取。映射就相当于拷贝了一份父组件传过来的 props
,作为子组件自己的状态。注意:子组件通过 setState
更新自身状态时,不会改变父组件的 props
配合 componentDidUpdate
,可以覆盖 componentWillReceiveProps
的所有用法
不把上一个props传递过来的原因:
1.在第一次调用getDerivedStateFromProps后,prevprops参数为null,需要在访问prevprops之前添加if-not-null逻辑检查
2.不把以前的props传递给这个函数,可以把之前不需要用的props释放掉,避免内存占用
该生命周期钩子触发的时机:
- 在 React 16.3.0 版本中:在组件实例化、接收到新的
props
时会被调用 - 在 React 16.4.0 版本中:在组件实例化、接收到新的
props
、组件状态更新时会被调用
getSnapshotBeforeUpdate
getSnapshotBeforeUpdate(prevProps, prevState)
:接收父组件传递过来的 props
和组件之前的状态,此生命周期钩子必须有返回值,返回值将作为第三个参数传递给 componentDidUpdate
。必须和 componentDidUpdate
一起使用,否则会报错
该生命周期钩子触发的时机 :被调用于 render
之后、更新 DOM
和 refs
之前
该生命周期钩子的作用: 它能让你在组件更新 DOM
和 refs
之前,从 DOM
中捕获一些信息(例如滚动位置)
配合 componentDidUpdate
, 可以覆盖 componentWillUpdate
的所有用法
异步生命周期
react的生命周期函数可以写成async的形式,使用async之后函数体中可以使用await
但是要注意使用,比如
1 | async shouldComponentUpdate() { |
正常情况下,如果shouldComponentUpdate返回的是false,组件不会被重新渲染。但是指定为async时返回promise.resolve,一个promise被转化为bool类型,绝对是true,因此shouldComponentUpdate返回的永远是true。
此外,fiber架构中使用异步渲染,render之前的生命周期函数会被调用多次,因此尽量保持render之前的生命周期不要产生副作用
所以async使用的生命周期一般是componentDIdMount和componentDitUpdate
旧版生命周期函数被弃用的原因
这三个生命周期容易被和误解和滥用
ComponentWillReceiveProps(Props):react 16之前使用componentWillReceiveProps来做props比较,然后更新组件的state。16及16之后推荐响应props更改的方法使用static getDerivedStateFromProps生命周期。该方法
getDerivedStateFromProps与ComponentDidUpdate配合,可以覆盖ComponentWillReceiveProps的所有用法
ComponentWillUpdate(Props){}:官方推荐用getDerivedStateFromProps回调里面处理传递过来的props,然后将异步数据放在componentDidupdate中。
在更新列表数据时,需要保持滚动条的位置。可以在getSnapshotBeforeUpdate获取dom的属性,例如offsetHeight、scrollHeight等,将React的值作为参数传递给componentDidUpdate
getSnapshotBeforeUpdate与ComponentWillUpdate配合,可以覆盖ComponentWillUpdate的所有用法
ComponentWillMount(){}:通常为了提前setState,防止二次渲染(第一次是空state渲染,第二次外部数据渲染),经常在ComponentWillMount请求数据。但是这样可能会导致异步获取外部数据不一定在渲染之前返回,也意味着组件也有可能会被渲染一次,为了后面新版本实现异步渲染,建议请求放在componentDidMount来调用。还有一个问题是在服务端渲染(Next.js)时,componentWillMount会导致服务端和客户端各渲染一次,而componentDidMount只在客户端渲染一次(有问题直接下掉)
新旧生命周期兼容
使用unsafe字样
使用react-lifecycles-compat
安装
1 | npm install react-lifecycles-compat --save |
使用
1 | import {polyfill} from 'react-lifecycles-compat' |
事件处理
React事件的命名采用小驼峰式命名,而不是纯小写
使用jsx语法是传入函数作为事件处理函数而不是字符串,这体现在写法上
1 | //html |
此外,阻止标签的默认行为需要在函数中显示使用preventDefault。
1 | //html中写法,阻止a标签默认打开链接 |
e是一个合成事件,类似于python函数的this参数
class声明的组件,事件不会自动绑定this,如果事件未写明绑定this,调用函数时this的值是undefined
在constuctor中绑定
1 | constructor(props){ |
绑定this参数的三种方式
直接使用bind绑定this参数
在构造函数中传参
箭头函数:()=>(){}
事件传递参数
1 | <button onClick={(e) => this.deleteRow(id,e)}>DeleteRow</button> |
通过箭头函数和function.prototype.bind实现
条件渲染
React 中的条件渲染和 JavaScript 中的一样,使用 JavaScript 运算符 if
或者条件运算符去创建元素来表现当前的状态,然后让 React 根据它们来更新 UI。
1 | class LoginControl extends React.Component { |
三目运算符与&&的区别
使用三目运算符进行计算时,如果组件中有副作用的函数会把两个组件全部计算完后再进行选择,而&&会进行断路,因此不必担心额外的副作用