一、学习主要内容
- 浏览器原理
- 建设高性能网站
- 组件开发
- 高级语法
- SSR
- Redux
- React Router
- React UI
- create-react-app
- 单元测试
- ReactNative
- 项目实战
1.1 获取react
Git地址:
- https://github.com/facebook/react
获取React:
- npm install react react-dom
注: 在ES5开发中,可以通过bower获取react.js和react-dom.js文件,我们使用最新版本中,只能使用ES6开发
二、 组件开发
2.1 创建虚拟DOM
核心库: react提供了createElement方法用来创建虚拟DOM
- 第一个参数表示虚拟DOM名称(也可以是组件名称)
- 第二个参数表示虚拟DOM的属性对象
- 从第三个参数开始,表示子虚拟DOM
2.2 虚拟DOM简介
虚拟DOM是一个普通js对象,存储一些必要属性来描述DOM
- type 表示虚拟DOM类型
- key 表示虚拟DOM的id
- ref 表示对真实DOM的引用
- props 表示虚拟DOM中所有属性对象
- children 表示虚拟DOM的子虚拟DOM
- 字符串 表示一个文本节点
- 对象 表示一个虚拟DOM
- 数组 表示多个虚拟DOM或者文本。
2.3 渲染虚拟DOM
浏览器端渲染库react-dom,提供了render方法,可以渲染虚拟DOM
- 第一个参数表示虚拟DOM
- 第二个参数表示真实DOM容器元素
- 第三个参数是回调函数,表示渲染完成执行的方法
2.4 组件
虚拟DOM与页面中的真实DOM相对应,因此一个虚拟DOM对应一个真实DOM
如果页面中有很多真实DOM,我们就要创建很多虚拟DOM
为了复用虚拟DOM,react提供了组件技术
定义组件就是一个类:
-
在ES5开发中,通过React.createClass方法创建组件类
-
在ES6开发中,通过class定义组件类
语法: class 组件类名{}
注意: 类名首字母大写。
2.5 JSX语法
我们创建虚拟DOM太麻烦了,不如在html页面中创建元素方便。
- 在xhtml中创建div:
- 在jsx语法中创建div:
JSX语法已经被纳入ES2017规范了
目前浏览器不支持JSX语法,因此我们要编译JSX语法。
编译jsx语法:
我们编译jsx语法使用: React编译器
我们既想编译ES6语法,也想编译jsx语法,就要同时使用es2015与react编译器。在webpack4.0中,我们要使用最新的编译器:
[’ @babel/preset-env ', ‘@babel/preset-react’]
扩展名:
- 为了区分ES5语法和ES6语法,我们将文件拓展名定义成.es
- 为了区分ES5语法和jsx语法,我们可以将文件的拓展名定义成.esx,.jsx,.tsx,.es6x…
- 为了语义化,react团队建议我们定义成.jsx
2.6 插值语法
插值语法: {}
- 是一个真正的js环境,可以书写复杂的表达式
- 由于插值语法提供的是真正的js环境。因此可以继续使用jsx语法
注: js语法与jsx语法可以嵌套使用
2.7 属性数据
// 需要安装prop-types: npm install prop-types
// 约束属性数据
Demo.proTypes = {
// 设置isRequire不能设置默认属性数据
data: ProTypes.array.isRequire
}
// 默认数据
Demo.defaultProps = {
data: ['默认数据']
}
虚拟DOM有四类属性: 自定义属性,元素属性,特殊元素属性,非元素属性。
- 自定义属性:是用来存储数据的。没有特殊含义
- html5中,自定义属性建议以data-开头
- 元素属性:对于元素来说有特殊的含义。列如:id,title,style等
- 注: style的属性值只能是对象。如:style={{ key: value }}外面的{}表示插值符号,内部的{}表示对象符号,样式名称要使用驼峰式命名
- 注: 如果样式属性值表示长度单位,并且单位是像素,则像素额可以省略
- 特殊元素属性:虚拟DOM有两个常见的特殊元素属性class和for。它们命中了关键字以及保留字。所以:class要写成className。for要写成htmlFor
非元素属性 – ref
非元素属性:
是由React拓展的,不是DOM元素默认的属性,如:ref,dangerouslySetInnerHTML,key,非元素属性是react提供的功能,类似vue中的指令
ref:
允许我们在组件中获取虚拟DOM对应的真实DOM元素(也可以是自定义组件)
列如:在componentDidMount(组件创建完成)方法中获取
分成两步:
1. 为元素设置ref属性
2. 在组件中,通过this.refs获取对应的元素
注意:如果获取的是元素,则是原生的DOM元素,因此要通过原生的DOM API操作。
通常在一些第三方插件中使用
我们直接操作真实DOM元素就跳过虚拟DOM环节,因此这些操作会被React忽略。无法被虚拟DOM优化,工作中慎用
非元素属性 – dangerouslySetInnerHTML
用危险的(不合法的)方式设置元素的内容。
不合法的方式有:
1. 不能使用行内式样式字符串
2. 不能直接使用特殊元素属性
3. 不能渲染标签等等...
我们可以借组dangerouslySetInnerHTML属性实现这些功能。属性值是对象,通过__html设置内容。
注意:
该属性也是直接对真实DOM元素设置innerHTML属性,因此跳过了虚拟DOM优化环节,工作中慎用
- 设置的内容要保证是安全的。否则可能会被攻击
非元素属性–key
为列表的虚拟DOM设置id
虚拟DOM可以包含一个子虚拟DOM或者文本,也可以包含多个子虚拟DOM(是数组)
当包含多个子虚拟DOM的时候,就是一个数组
此时要为每一个成员设置key属性。
注意: 每一个数组中,key属性值要唯一并且稳定、可预测
代码使用
import React, { Component } from 'react'
import { render } from 'react-dom'
// 数据
let msg = '<span>span</span>'
let data = ['如果当时', '忙着微笑', '和哭泣啊']
class Demo extends Component {
componentDidMount() {
console.log(this, 'this')
this.refs.ickt.style = "color: red"
}
//定义创建列表的方法
createList() {
// return data.map((item, index)=> {
// return <li key={index}>{item}</li>
// })
// 省略
return data.map((item, index) => <li key={index}>{item}</li>)
}
render() {
console.log(this.createList(), 'this')
return (
<div>
{/* {自定义数据属性} */}
<h1 data-ickt="hello">标题</h1>
{/* {元素属性} */}
<h1 title="hello" style={{ color: 'red', backgroundColor: 'green' }}>啦啦啦啦</h1>
{/* {特殊元素属性} */}
<h1 className="lalala">特殊元素属性</h1>
<label htmlFor="usernmae">用户名</label>
<input type="text" id="usernmae" />
{/* {点击用户名,input获取焦点} */}
{/* {非元素属性} */}
<h1 data-num="100" ref="ickt">我是非元素属性</h1>
{/* dangerouslySetInnerHTML */}
<h1 dangerouslySetInnerHTML={{ __html: msg }}>
</h1>
{/* 通过方法创建数组 */}
{this.createList()}
</div>
)
}
}
render(<Demo></Demo>, app)
2.8 组件属性
为了增强组件的适用性,我们要为组件定义属性
- 属性分类:
- 虚拟DOM有四类属性:自定义数据属性 ,元素属性,特殊元素属性,非元素属性。
- 组件只有一类属性数据,就是自定义数据属性
- 传递属性:组件是对虚拟DOM的封装,因此为组件传递属性数据跟虚拟DOM传递属性数据的语法是一样的。
- 虚拟DOM传递属性数据
- 组件传递属性数据
- 使用属性: 在组件中,通过this.props 获取属性数据。
注意:属性数据是组件外部维护的数据,只能在组件外部修改,不能在组件内部修改
import React, { Component } from 'react'
import { render } from 'react-dom'
// 组件
class Demo extends Component {
// 静态数据,第二种方式,通过static定义
get num() {
return 100
}
static getNum() {
return this.num
}
createList() {
// 使用组件属性数据
return this.props.data.map(item => <li key={item}>{item}</li>)
}
render() {
return (
<ul>
{this.createList()}
</ul>
)
}
}
// 静态数据,第一种方式,外部打点添加, react推荐使用
Demo.color = 'red';
Demo.getColor = function () {
return 'blue'
}
// 默认属性数据
Demo.defaultProps = {
data: ['默认数据']
}
console.log(Demo.num, 's')
let data = ['1', '2', '3', '4']
render(<Demo data={data}></Demo>, app)
render(<Demo data={['lalal']}></Demo>, app2)
2.9 DOM事件
react中,为虚拟DOM绑定事件与真实的DOM绑定事件的语法是类似的
真实DOM绑定事件
虚拟DOM绑定事件
注意:
- 事件名称首字母要大写
- 事件回调函数要定义在组件中
- 事件回调函数不要执行(后面不要添加参数集合{})
- 默认参数就是事件对象
- this指向undefined
三、组件生命周期
为了描述组件创建,存在、销毁的过程,react也为组件提供了生命周期技术
-
创建期
-
存在期
-
销毁期
-
创建期共分为五个阶段,描述组件创建的过程。
-
在ES5开发中: getDefaultProps, getInitialState, componentWillMount, render, componentDidMount
-
在ES6开发中,创建期的前两个阶段方法有所改变
3.1 创建期
-
定义组件默认属性数据: defaultProps静态属性。此时组件尚未创建。属性值就是默认的属性数据。没有为组件传递属性数据,组件救护使用该默认数据
-
构造函数(初始化状态数据): constructor。构造函数有个参数: 属性数据,上下文数据。
构造函数中,要使用super方法实现构造函数式继承。我们要向super方法传递属性数据和上下文数据。如果没有传递属性数据和上下文数据: 此时参数props与this.props不用,并且参数context与this.context不同。如果传递了属性数据和上下文数据:此时参数props与this.props全等,并且参数context与this.context全等.
所以工作中建议传递属性数据和上下文数据,但是由于工作中,上下文数据不会经常使用。所以通常我们只传递属性属性就可以了。在构造函数中,我们为this.state赋值,实现状态数据的初始化
我们可以用属性数据和上下文数据为状态数据赋值,实现数据由外部流入内部
这么做的原因是: 可以在组件内部维护(修改)属性数据和上下文数据(不会影响其他组件)
-
组件即将构建: componentWillMount。此时已经有了属性数据,状态数据和上下文数据,但是还不能获得虚拟DOM。工作中,我们可以在该阶段发送请求,初始化插件等
-
渲染输出虚拟DOM: render。这个方法主要用于渲染虚拟DOM。返回值就是渲染的虚拟DOM,有且只有一个根元素。注意:render。这个方法主要负责渲染,所以不要在该方法中实现业务逻辑(如发送请求等)
- 不要在该方法中,尝试修改数据,(如状态数据)。虚拟DOM是在返回值中创建的,因此在render方法中,无法获取虚拟DOM对应的真实DOM
-
组件构建完毕: compomentDidMount. 此时组件就创建出来了,已经有了属性数据,状态数据,上下文数据以及虚拟DOM。该方法执行完毕,标志着创建期的结束,存在期的开始。我们可以在该方法中,绑定事件(全局),发送请求,使用插件等等。
注: 创建期的方法在组件的一生中,只能执行一次。后面四个方法中this都指向组件,后三个方法没有参数。
3.2 获取DOM元素
渲染库react-dom提供了findDOMNode方法,可以获取虚拟DOM对应的真实DOM元素
获取的是原生DOM,因此要用源生DOM API去操作
注意: 工作中尽量不要这样去操作虚拟DOM,因为对原生DOM的操作是会被react无视掉
3.3 存在期
存在期跟创建期一样,也分为五个阶段,来描述组件数据更新的过程
一旦组建的属性数据,状态数据或者上下文数据改变,组件就会进入存在期。五个阶段:
-
组件即将接收新的属性数据: componentWillReceiveProps。只有属性数据改变,才会执行该阶段方法,状态数据改变不会执行。第一个参数表示新的属性数据。this上的还是旧的属性数据和旧的状态数据。我们可以用属性数据更新状态,实现外部的数据存储到内部。讲外部数据存储到组件内部是该阶段的唯一作用。此时新的属性数据和新的状态数据一同进入第二个阶段方法
-
组件是否应该更新:shouldComponentUpdate。第一个参数表示新的属性数据。第二个参数表示新的状态数据。this上的还是旧的的属性数据和旧的状态数据。必须有返回值,表示是否更新:true,表示更新;false,表示不更新,工作中,通常比较属性数据是否改变或者状态数据是否改变来决定是否更新
作用:起到更新优化的作用(如:在高频事件中应用)
-
组件即将更新:componentWillUpdate。第一个参数表示新的属性数据。第二个参数表示新的状态数据。this上的还是旧的属性数据和旧的状态数据。数据在该阶段还没有更新,该阶段执行完毕,数据才会更新,我们可以在该阶段调整插件等等。
-
组件渲染虚拟DOM:render。没有参数,数据已经更新了,与创建期render是同一个方法。this上的已经是新的属性数据和新的状态数据了。所以渲染虚拟DOM的时候,就可以使用这些新的数据了
-
组件更新完毕: componentDidUpdate。第一个参数表示旧的属性数据。第二个参数表示旧的状态数据。this上的已经是新的属性数据和新的状态数据了。虚拟DOM也可以更新了,所以在该阶段新数据,旧的数据都可以访问到,我们可以在该阶段,继续请求数据,处理插件,调整事件等等。组件已经更新完毕,只是一次更新结束,存在期仍然存在
注: 这些方法中的this都指向组件
四、高级语法
4.1 PureComponent
该组件主要是为了对类组件做性能优化的
- 功能类似shouldComponentUpdate方法
使用方式与Component一样:
- class组件类extends PureComponent{}
- 此时当组件的数据改变的时候,组件才会更新
- 只对类组件生效,对函数组件无效
- 只对数据(属性,状态)做浅层的比较,如果是引用类型的数据,不会深比较。此时还得用Component的shouldComponentUpdate方法
4.2 memo
React提供了memo方法,可以对函数组件做优化
memo(Comp, (currentProps, nextProps)=> {
// currentProps: 表示当前的属性数据
// nextProps: 表示新的属性数据
// 必须有返回值,表示是否不更新:true表示不更新、false表示更新与shouldComponentUpdate返回值正好相反,true表示更新
注意:memo方法只能处理函数组件
memo方法返回值是一个新组件,原来的组件不受影响,返回的新组件才能被优化
用一个函数处理组件,并返回一个新组件,这个新组件就是高阶组件,这个方法(函数)叫高阶方法
})
4.3 cloneElement
有时候想复制一个虚拟DOM,可以使用cloneElement方法
用法与createElement方式类似
cloneElement(vdom, props, ...children)
第一个参数表示虚拟DOM
第二个参数表示属性对象
从第三个参数开始表示传递给新的虚拟DOM的子内容
cloneElement与createElement方法一样,也有jsx语法的等价语法。
<vdom.type key={value}{...props}></vdom.type >
定义虚拟DOM
import React, { Component, cloneElement } from 'react'
let title = <h1 className="demo"> hello </h1>
let obj = {
id: 'test',
style: {
color: 'red',
fontSize: 40
}
}
class App extends Component {
render() {
return (
<div>
// 使用虚拟DOM
{ title }
// 复制虚拟DOM
{ cloneElement(title, {title: '专业前端培训学校'}) }
// jsx等价语法
<title.type title="专业前端培训学校" {...obj}>爱创课堂</title>
</div>
)
}
}
4.4 Fragment
作用类似Vue中的template元素
是用来定义一个模板的组件
- 该组件可以作为根节点,但是不会渲染到页面中
工作中:
1. 用来渲染多个兄弟根元素 2. 避免在特定元素之间,引入新元素。
render() { return ( <Fragment key={item}> <li>{item}</li> </Fragment> )}
4.5 错误边界组件
在一个组件中,如果某个子组件出现了错误,整个页面都无法渲染了
- 子组件中的错误冒泡到父组件中,影响了父组件的渲染。
所以react提供了错误边界技术,让错误冒泡到错误边界组件中,这样就不会影响父组件的渲染了
错误边界组件使用:
只需要定义componentDidCatch方法,就可以捕获错误,避免错误冒泡 第一个参数错误信息, 第二个参数表示错误描述信息 在错误边界组件中,可以定义静态方法getDerivedStateFromError。 该方法的返回值表示错误产生的时候,修改的状态数据
class Error extends Component { constructor(props) super(props) this.state = { hasError: fasle; } // 在这更新状态数据,不会执行存在期,优化性能 static getDerivedStateFromError() { return ( hasError: true ) } // 获取错误,不要继续冒泡 componentDidCatch(...args) { console.log(args) } render() { if (this.state.hasError) { return <h1>has error</h1> } }}
4.6 异步组件
Children
不论是虚拟DOM还是组件,其Props属性,都有一个children属性,代表子内容
可以是文本,对象,数组
我们可以直接通过children属性获取内容
异步组件
将所有的组件都打包在一起,会导致文件很大,加载很慢,影响用户体验
所以我们要使用异步组件,让我们可以异步的加载组件
我们要使用import方法来异步加载,需要安装插件:
babel-plugin-syntax-dynamic-import
注: webpack 4.0 的babel插件已经支持异步加载了
4.7 组件的约束性
用户可以与组件中的表单元素交互,这样就产生了数据,这些数据存储在哪里决定了组件的约束性
如果交互产生的数据存储在元素自身,该组件就是非约束性组件
如果交互产生的数据存储在组件状态中,该组件就是约束性组件
非约束性组件
交互产生的数据存储在元素自身,该组件就是一个非约束性组件。获取数据,存储数据都要通过该元素。
默认值:
- 我们通过defaultValue或者defaultChecked属性定义默认值。都是非元素属性。
获取数据:
-
为元素设置ref属性
- 通过this.refs获取元素,再通过元素获取数据
修改数据:
-
为元素设置ref属性
- 通过this.refs获取元素,再修改元素的数据
render() { return ( <input type="text" defaultValue="hello" defaultChecked ref="username"></input> )}
Ref
react提供了一种新的创建ref对象的方式
通过createRef可以创建一个ref对象
目的:代替Ref字符串方式
优势:允许我们在组件外部获取组件内部的元素
使用ref对象分成三步
- 在构造函数中,创建ref对象,并存储在组件自身
- 让ref对象指向元素
- 通过ref对象的current属性获取元素
import React, {component, createRef} from 'react'constructor(props) { super(props) // 创建ref对象 this.user = createRef()}// 获取数据getData() { console.log(this.user.value)}// 修改数据changeDate() { this.user.current.value = "xiugai"}render() { return ( <input ref={this.user} defaultValue="hello"></input> )}
约束性组件
交互产生的数据存储在组件状态中,该组件就是一个约束性组件。获取数据,修改数据都通过组建的状态
默认值 :
- 为value属性和checked是属性绑定组件的状态数据
- 在onChange方法中,监听数据的改变,并更新状态
- 如果是输入框,我们还可以做表单校验。判断内容是否合法:合法则更新状态、不合法则不更新状态
获取数据“:
- 就是获取组件的状态数据
修改数据:
- 就是修改组件的状态数据
4.8 高阶组件
在react组件中,为了保证组件基类的完整性,我们对组件扩展功能的时候,react不允许我们修改组件的基类
如果对组件扩展功能,React提供了高阶组件的技术:类似装饰者模式,不用直接修改原来的组件,而是定义一个新组件,对原来组件做包装
使用高阶组件技术,需要定义一个高阶函数(方法)
参数是原组件,以及给高阶组件传递的数据。在函数中,我们创建一个新组件,
在render方法中,我们渲染原来的组件并传递相应的属性数据
我们对新组件进行包装(扩展),以此来实现对原来组件的扩展(没有被修改)。
返回新组件,使用的时候,使用的是新组件
import React, { Component } from 'react'import { render } from 'react-dom'class App extends Component { constructor(props) { super(props) this.state = { msg: 'hello' } } render() { let { msg } = this.state return ( <div> <input type="text" value={msg} onChange={e => this.setState({ msg: e.target.value })} /> <hr /> <Demo msg={msg}></Demo> <hr /> <IcktDemo msg={msg}></IcktDemo> </div> ) }}class Demo extends Component { // 组件即将接受新的属性数据 render() { return ( <div> <h1>{this.props.msg}</h1> </div> ) }}// 定义高阶方法function ickt(comp) { // 定义组件 class Ickt extends Component { // 通过对新组件的扩展,实现需求,不影响原来的组件 // 渲染方法中,输出原来的组件 render() { // 将ickt接受的属性,传递给demo组件 console.log(this.props) return <Demo {...this.props}></Demo> } } return Ickt}// 通过高阶方法,创建高阶组件let IcktDemo = ickt(Demo)render(<App></App>, app)
代码实现memo
import React, { Component, PureComponent, memo } from 'react'import { render } from 'react-dom'class App extends Component { constructor() { super() this.state = { isShow: 'none' } } componentDidMount() { window.addEventListener('scroll', () => { if (scrollY > 200) { this.setState({ isShow: 'block' }) } else { this.setState({ isShow: 'none' }) } }) } render() { return <NewDemo {...this.state}></NewDemo> }}function icktMemo(Comp, fn) { class Ickt extends Component { shouldComponentUpdate(newProps) { return !fn(this.props, newProps) } render () { return <Comp {...this.props}></Comp> } } return Ickt}let Demo = props=> { console.log(props, 'props') return ( <div style={{ position: 'fixed', right: 100, bottom: 50, background: 'red', width: 40, height: 40, color: 'black', padding: 20, cursor: 'pointer', textAlign: 'center', lineHeight: '20px', display: props.isShow }}>返回顶部</div> )}let NewDemo = icktMemo(Demo, (currentProps, nextProps)=> { return currentProps.isShow === nextProps.isShow})render(<App></App>, app)
4.9 ref转发
ref转发就是让我们组件的外部访问组件内部的元素。
我们使用ref有两种方式
- 使用ref字符串
- 在组件内部通过this.refs获取对应的元素(组件内部获取)
- 通过createRef创建ref对象
- 通过ref对象的current属性获取对应的元素(允许在组件外部获取)
注: ref 属性既可以添加给虚拟DOM,也可以添加给组件。
import React, { Component, createRef, forwardRef } from 'react'import { render } from 'react-dom'let newRef = createRef()class App extends Component { render() { return ( <div> {/* {不建议在组件内部访问组件外部的变量} */} {/* {指向了组件就不能再指向虚拟DOM了} */} <h1 ref={this.props.refData}>1-1-1</h1> {/* {一个ref对象只能指向一个虚拟DOM} */} <h1 ref={this.props.refData}>1-1-2</h1> <h1>3-3-3</h1> </div> ) }}// 函数组件没有状态属性和生命周期// let App = props=> {// console.log(props, 'props')// return (// <div>// <h1 ref={props.refData}>'巨人'+1-1-1</h1>// <h1>'巨人'+2-2-2</h1>// <h1>'巨人'+3-3-3</h1>// </div>// )// }// 只是传递,不要指向组件// 函数组件不能直接指向组件,会报错 (使用forwardRef())// let DealApp = forwardRef((props, ref)=> {// // 返回值中,返回函数组件 取消了ref指向// console.log(props, ref, 'DealApp')// return <App {...props} refData={ref}></App>// })// render(<DealApp ref={newRef} color="red"></DealApp>, app, ()=> {// console.log(newRef)// })// 取消类组件的ref指向let DealApp = forwardRef((props, ref)=> { console.log(props,ref, 'props') return <App {...props} refData={ref}></App>})render(<DealApp ref={newRef} color='red'></DealApp>, app, ()=> { console.log(newRef)})
ref转发在高阶组件中的使用
import React, { Component, forwardRef, createRef } from 'react'import { render } from 'react-dom'class App extends Component { constructor() { super() this.state = { msg: 'msg' } this.ickt = createRef() } render() { return ( <div> <h1>App</h1> <Demo {...this.state}></Demo> <IcktDemo {...this.state} ref={this.ickt}></IcktDemo> </div> ) }}class Demo extends Component { render() { console.log(this.props, 'Demo') return ( <div> <h1 ref={this.props.icktRef}>{this.props.msg}</h1> </div> ) }}function ickt(Cmp) { class Ickt extends Component { render() { return ( <Cmp {...this.props}></Cmp> ) } } return forwardRef((props, ref) => { return <Ickt {...props} icktRef={ref}></Ickt> })}let IcktDemo = ickt(Demo)render(<App></App>, app)
5.0 Hook
hook是react新版本中提供一项技术,用来为函数组件扩展功能的
函数组件是一个十分简单的组件,是对类组件的简化,因此没有继承组件基类,不具有组件的行为。例如:状态数据,生命周期等等。所以为了让函数具有这些行为,我们可以用hook方法对函数组件做扩展。
使用状态数据
语法: let [数据, 修改数据的方法] = useState(默认数据值); 修改数据的方法: 参数就是新的状态书 在函数组件中,用’修改数据的方法‘去修改数据,此时函数组件将进入存在期,更新虚拟DOM。
使用生命周期的方法
语法:use Effect(fn)fn代表componentDidMount以及componentDidUpdate方法...hook为函数组件扩展了大量的功能useEffect(() => { document.title = `You clicked ${count} times`;}, [count]); // 仅在 count 更改时更新
五、 SSR
5.1 服务器端渲染
前端渲染的问题
- 白屏时间长,影响用户体验
- 不利于SEO优化
所以我们要使用服务器端渲染,来解决这些问题
React实现了多端适配,为不同的端提供了不同的渲染库,React核心库只负责创建虚拟DOM和组件
在web端渲染,使用react-dom渲染库
在服务器渲染,使用react-dom/server.js渲染库
服务器端渲染库
react-dom/server.js提供了在服务器端渲染的方法
renderToString 将应用程序渲染成模板字符串
renderTostaticMarkup 将应用程序渲染成模板字符串,并且会删除不必要的属性
例如: data-reactroot等属性
该方法减少了文件大小,因此常常用在渲染静态页面中
服务器端渲染的问题是:无法绑定交互
六、 Redux
redux是一个通用的框架,因此在不同的框架中使用,要安装相应的插件
- 在react中使用,要安装react-redux
- vuex之所以能够解决组件之间的通信,是因为vuex为每一个组件都扩展了
- 发布信息的方法:commit和dispatch
- 获取store的数据:state和getters
因此想在react中,使用redux解决通信,就要让每一个组件获取store中的state数据,以及发布信息的方法(dispatch)
react-redux提供了connect方法以及Provider组件,可以解决上述问题
redux是通过属性传递数据实现的。
6.1 connect
该方法是用来为组件提供了state数据以及dispatch方法的
两个参数都是函数
- 第一个参数函数表示: 如果为组件的属性扩展state数据
- 参数是state数据,返回值是为组件的属性扩展的state数据(还可以扩展更多的数据)
- 第二个参数函数表示: 如何为组件的属性扩展dispatch方法
- 参数表示dispatch方法,返回值是为组件的属性扩展的dispatch方法(还可以扩展更多的方法)connect方法返回一个函数(高阶函数)
该函数创建的组件(高阶组件)可以接受state数据集和dispatch方法
没有通过该函数处理的组件(包括原组件)是不会接受state数据和dispatch方法的
6.2 Provider
该组件是为组件提供store数据源的
- 通过store属性提供数据源。属性值就是store对象
- 我们可以将组件放在该组件内部,就可以接受数据了
在react中使用redux共分两步
- 通过connect方法扩展组件,接受state数据和dispatch方法
- 通过Provider组件提供store数据源
让其他的组件接受state数据和dispatch方法有两种方式:
- 可以通过父子组件通信的方法,传递数据和方法
- 继续使用connect方法得到的高阶函数扩展组件
代码使用
import React, { Component } from 'react'import { render } from 'react-dom'import { createStore } from 'redux'import { connect, Provider } from 'react-redux'// 类型给const ADD = 'ADD'const DEL = 'DEL'let addNum = { type: ADD, data: 5 }let delNum = { type: DEL, data: 5 }function reducer(state = 0, action) { // 根据action维护state数据 switch (action.type) { case ADD: state += action.data break; case DEL: state -= action.data default: break; } return state}let store = createStore(reducer)class App extends Component { constructor(props) { super(props) } render() { const { state, dispatch, addNum } = this.props return ( <div> <h1>{'state:' + this.props.state}</h1> <Slider state={state} dispatch={dispatch} addNum={addNum}></Slider> {/* <Demo></Demo> */} {/* 使用扩展后的高阶组件,才能获取数据 */} <DealDemo></DealDemo> </div> ) }}class Slider extends Component { render() { // console.log(this, 'Slider') const { state, dispatch, addNum } = this.props return ( <div> <h2> <button onClick={e => addNum(5)}>增加5</button> <button onClick={e => addNum(10)}>增加10</button> {'numSlider :' + state} </h2> </div> ) }}class Demo extends Component { render() { const { state, dispatch } = this.props; return ( <div> <h2> <button onClick={e => dispatch(delNum)}>减少5</button> {'numDemo :' + state} </h2> </div> ) }}let deapFn = connect( state => ({ state }), dispatch => ({ dispatch, addNum(data) { dispatch({ type: 'ADD', data }) } }))let DeapApp = deapFn(App)let DealDemo = deapFn(Demo)render( <Provider store={store}> <DeapApp></DeapApp> </Provider> , app)
6.3 action
静态action
- 静态action就是一个对象:type定义类型,其他属性定义数据
- 我们目前定义的action都是静态action
- 由于静态action中数据是不变的,因此使用的时候不够灵活。适用性不强
动态action
- 动态action是一个方法,使用的时候要执行,并传递数据
- 在方法中,返回action对象,action对象中,存储参数数据
- 这样action中的数据是灵活可变的,适用性强,工作中常用。动态action与拓展属性方法的区别是:
- 拓展属性方法可以灵活传递数据,但是会污染组件的属性对象
- 动态action可以灵活传递数据,并且不会污染组件的属性对象
6.4 异步action
在一个组件中发送请求,获取的数据要在其他的组件中使用。此时可以使用异步action
异步action跟动态action类似
- 是一个方法,参数是传递的数据
- 返回值是一个方法
- 第一个参数是dispatch(常用)
- 第二个参数是getState
- 在方法中,做异步事情。异步结束之后,再发布同步信息
中间件
reedux默认不支持异步action,想使用异步action要为redux安装中间件插件
redux模块提供了applyMiddleware方法
- 参数是中间件
- 返回时是一个方法,用来拓展createStore方法
- 返回值是一个新的createStore方法
- 用该方法创建的store就支持异步action了
异步action中间件插件: redux-thunk
七、Router
路由
react有三个特点:虚拟DOM,组件化开发,多端适配
所以react路由为了适配多端,为不同的端提供了不同的路由模块
- 在web端,使用react-router-dom模块
- 在native端,使用react-router-native模块
它们都依赖react-router模块
所以要安装两个模块 : react-router, react-router-dom
注意: 不同的react版本使用路由的方式不同
使用路由
-
第一步通过Switch组件定义路由渲染位置,通过Route组件定义路由规则
path 定义规则(与vue规则一样) name 定义名称component 定义组件 exact 是否精准匹配注意:可以不适用Switch组件,此时就不能保证同时只显示一个页面了
-
第二步 确定路由渲染策略
BrowserRouer基于path策略实现的,类似Vue中的history策略 需要服务器端配合:做重定向。实现的是多页面应用HashRouter基于hash策略实现的 实现的是单页面应用,用路由策略组件渲染应用程序组件
路由重定向
路由重定向:在React路由中,通过Redirect组件实现路由重定向
from 定义匹配的地址
to 定义重定向的地址
默认路由:在React路由中,我们将path属性匹配,既可以定义默认路由。
由于默认路由匹配的很广,因此常常写在最后面。
路由导航:为了切换页面,React路由提供了路由导航组件:Link组件
通过to属性定义切换的地址,即使是hash策略,不要以#开头
Link组件只能渲染成a标签。
与a标签相比,Link组件可以适配不同的路由策略
路由数据
通过Route组件渲染页面组件可以通过属性获取路由数据
- match:包含对路由规则解析的数据,如:path,url,isExact,params(动态路由数据)
- location:包含当前的真实地址的信息(类似全局的location),如:pathName,search,hash等
- history:包含对路由操作的方法,(类似全局的history),如:push,replace,go,goBack,goForward等
没有通过Route组件渲染组件不具有路由数据,想获取路由数据,有三种方式
- 父子组件通信的方式传递数据可以传递部分数据
- 继续使用Route组件渲染该组件
- 使用withRouter方法扩展高阶组件