在上面的示例中,我们只依赖了浏览器原生支持的特性。这就是为什么我们使用了 JavaScript 函数调用来告诉 React 要显示什么, 但是,React 还提供了一种用 JSX 来代替实现的选择:
JSX——JavaScript XML,是React定义的一种类似于XML的拓展语法,其作用是用来创建React虚拟DOM对象JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式。JSX 可能会使人联想到模版语言,但它具有 JavaScript 的全部功能。React 并没有采用将标记与逻辑进行分离到不同文件这种人为地分离方式,而是通过将二者共同存放在称之为“组件”的松散耦合单元之中,来实现关注点分离。
遇到< 开头的代码,以标签语法解析,html同名标签转换为html同名元素,其他标签需要特别解析,往往是组件
遇到以{开头的代码,以JS语法解析。
由于 JSX 会编译为 React.createElement 调用形式,所以 React 库也必须包含在 JSX 代码作用域内。即使没有显式地调用React及其组件,依然需要引入它
用户自定义的组件必须的大写字母开头
一个JSX语法片段只能返回一个根元素,所以必须有外层元素包裹。如果需要返回多个元素,Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。短语法是<>>
Hello
World
布尔类型、Null 以及 Undefined 将会忽略, 以下元素渲染结果相同
{false}
{null}
{undefined}
{true}
这有助于在特定条件下渲染元素
{showHeader && }
JSX本身也是表达式,在编译之后,JSX 表达式会被转为普通 JavaScript 函数调用,并且对其取值后得到 JavaScript 对象。可以:
在 if 语句和 for 循环的代码块中使用 JSX
将 JSX 赋值给变量
把 JSX 当作参数传入
从函数中返回 JSX
JSX中可以通过使用引号,来将属性值指定为字符串字面量:
const element =
;
因为 JSX 语法上更接近 JavaScript 而不是 HTML,所以 React DOM 使用 camelCase
(小驼峰命名)来定义属性的名称,而不使用 HTML 属性名称的命名约定。
1.4 模块与组件和模块化与组件化的理解 1.4.1 模块
理解:向外提供特定JS功能的程序,一般是一个JS文件
为什么需要模块:现代化前端工程,尤其是大型工程,JS代码更多更复杂,需要更好的机制去优化开发环节的便利以及生产环节的加载性能
作用:提高JS的复用能力,提高编程效率,提高运行效率
1.4.2 组件
理解: 组件是用来实现特定功能效果的代码集合,包括结构、样式与行为(HTML/CSS/JS)
为什么需要组件:现代前端工程中一个界面的功能更加复杂,需要进行拆分并关注当前最小单元
作用:提高JS的复用能力,提高编程效率,提高运行效率
React的组件分为:(1)继承React的class类组件(2)函数组件
第2章:React面向组件编程 2.1 基本理解和使用 2.1.1 自定义组件 // 方式一:工厂函数组件
const MyFactoryComp = (props) => {
return 函数组件
}
// 方式二:ES6类组件
class MyClassComp extends React.Component {
render () {
return 类组件
}
}
// 渲染组件
ReactDom.render( , document.getElementById('root'))
2.1.2 注意事项
组件名首字母必须大写,没有大写的在引用之前都需要先赋予首字母大写的别名,然后再引用别名
虚拟DOM元素只能有一个根元素,如果不方便插入根元素可使用<>>
虚拟DOM元素必须有结束标签
2.1.3 渲染过程
React创建内部虚拟DOM对象
将其解析为真实的DOM片段
插入到指定元素中,并替换元素下所有内容,所以#root
元素节点的内容写了也是无效的
2.2 组件三大属性之state 2.2.1 定义及定义方法 函数组件没有state
,类组件才可以维护state
,但是函数组件也有自己的hook来进行内部数据管理。类组件中有两种方式声明state
并进行初始赋值:
在class
构造函数constructor
中进行初始赋值
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
Hello, world!
It is {this.state.date.toLocaleTimeString()}.
);
}
}
直接定义state
对象变量
class Clock extends React.Component {
state = {date: new Date()}
render() {
return (
Hello, world!
It is {this.state.date.toLocaleTimeString()}.
);
}
}
2.2.2 示例 class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
Hello, world!
It is {this.state.date.toLocaleTimeString()}.
);
}
}
ReactDOM.render(
,
document.getElementById('root')
);
如何理解上述代码的调用顺序
当 被传给 ReactDOM.render()
的时候,React 会调用 Clock 组件的构造函数。此时第一次更新state
之后 React 会调用组件的 render()
方法。组件第一次被渲染
当 Clock 的输出被插入到 DOM 中后,React 就会调用 ComponentDidMount()
生命周期方法。设置 tick()
方法
每秒都会调用一次 tick()
方法。更新setState()
,重新调用 render()
方法
组件被移除时,调用 componentWillUnmount()
生命周期方法,计时器被移除
2.2.3 注意事项
不要直接修改state,需要使用setState()
,这个函数可以接收一个对象,也可以接收一个函数,构造函数和声明处是唯一可以直接给state
赋值的地方
state
的更新可能是异步的,不能依赖他们的值来更新下一个状态,或者在代码后立即使用新值做一些事情
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
state
的更新会被合并,当你调用 setState()
的时候,React 会把你提供的对象合并到当前的 state
2.3 组件三大属性之props 2.3.1 定义及定义方法 组件的概念类似于JS函数,该函数可以理解为:入参——即组件标签上的所有属性(attributes)以及子组件(children)转换为单个对象传递给组件,即props,返回值:返回React元素 在函数组件中,props是写明的唯一参数,在类组件中,props可以在组件内部直接引用
2.3.2 示例 // 函数组件举例
function Welcome(props) {
return Hello, {props.name} ;
}
// 类组件举例
class Welcome extends React.Component {
render() {
return Hello, {this.props.name} ;
}
}
// 组件调用
const element = ;
ReactDOM.render(
element,
document.getElementById('root')
);
上例中,name="Sara"
属性被赋予了组件,就会被转换为{name: 'Sara'}
作为props
传递给组件
2.3.3 注意事项
props是只读的,无论是函数组件还是类组件,都不能改变自身的props,所有组件必须像维护纯函数一样保证props不被修改。
2.4 组件三大属性之refs 2.4.1 定义及定义方法 在某些情况下,你需要在典型数据流之外强制修改子组件。如获取已渲染元素在屏幕上宽高、手动给input聚焦等,React提供了refs来获取真实的DOM。 refs需要像state一样进行初始赋值
2.4.2 示例 Refs 是使用 React.createRef()
创建的,并通过 ref
属性附加到 React 元素。在构造组件时,通常将 Refs 分配给实例属性,以便可以在整个组件中引用它们。
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef(); // 将 Refs 分配给实例属性, 创建一个 ref 来存储 myRef 的 DOM 元素
}
componentDidMounted () {
// 对该节点的引用可以在 ref 的 current 属性中被访问
const node = this.myRef.current;
...
}
render() {
return
;
}
}
2.4.3 注意事项
如果一件事情可以通过声明式来完成,那最好不要使用 refs
refs
只能在类组件中使用,不能在函数组件中使用,因为它没有实例
ref
会在 componentDidMount
或 componentDidUpdate
生命周期钩子触发前更新。挂载前,给current
属性传入DOM元素,卸载前,传入null
元素
2.5 事件处理 2.5.1 示例 class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// 为了在回调中使用 `this`,这个绑定是必不可少的
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(state => ({
isToggleOn: !state.isToggleOn
}));
}
render() {
return (
{this.state.isToggleOn ? 'ON' : 'OFF'}
);
}
}
ReactDOM.render(
,
document.getElementById('root')
);
2.5.2 注意事项
通过 onXxx
(注意驼峰式)属性指定组件的事件处理函数(函数而非字符串)
// 原生事件
Activate Lasers
// React事件
Activate Lasers
不能通过返回 false
的方式阻止默认行为。你必须显式的使用 preventDefault
。
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}
return (
Click me
);
}
参数 e
是一个合成事件,并且作为默认第一个参数传递,可以在回调函数内直接调用
需要谨慎对待事件回调函数中的 this
,class
的方法默认不会绑定 this
,如果函数内部需要调用类组件的 this
, 需要显式绑定或者采用箭头函数声明回调函数
2.6 组件生命周期 2.6.1 生命周期定义 生命周期是组件对象从创建到销毁所经历的特定的生命周期阶段。React组件对象定义了一系列的钩子函数,可以在生命周期的特定时刻执行这些函数。我们在定义组件中可以重写生命周期钩子函数,达到我们自己的目的。
2.6.2 生命周期流程 进行细分可以分为:
阶段
调用方法
挂载阶段
constructor()
/ static getDerivedStateFromProps()
/ render()
/ componentDidMount()
更新阶段
static getDerivedStateFromProps()
/ shouldComponentUpdate()
/ render()
/ getSnapshotBeforeUpdate()
/ componentDidUpdate()
卸载
componentWillUnmount()
错误处理
static getDerivedStateFromError()
/ componentDidCatch()
2.6.3 注意事项
render()
方法是 class
组件中唯一必须实现的方法
在 constructor()
函数中不要调用 setState()
方法, 要避免在构造函数中引入任何副作用或订阅
避免将 props 的值复制给 state
componentWillUnmount()
中需要卸载具有副作用的一些方法,比如定时器、取消网络请求、清除挂载时的订阅等
2.7 HOOK ES6 class
组件可以在内部定义 state
进行数据管理,函数组件现在也有了类似功能的 Hook
,可以在不编写 class
的情况下使用 state
以及其他的React特性。以前我们说,复杂组件用类组件,函数组件只适合简单组件,有了Hooks
支撑之后,函数组件也可以向类组件一样实现更加复杂的功能。 Hook 是一些可以让你在函数组件里“钩入” React state
及生命周期 等特性的函数。其中最常用的就是 State Hook
和 Effect Hook
,前者提供了 useState
方法向组件内部添加一些 state
,后者则提供了useEffect方法来模拟 componentDidMount
、componentDidUpdate
和 componentWillUnmount
生命周期。
2.7.1 State Hook 示例
// 类组件写法
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render () {
return (
<>
this.setState({ count: this.state.count + 1 })}>
Click me
You clicked {this.state.count} times
>
)
}
}
// 函数组件写法
import React, { useState } from 'react';
function Example() {
// 声明一个叫 “count” 的 state 变量
const [count, setCount] = useState(0);
const [fruit, setFruit] = useState('banana');
return
<>
setCount(count + 1)}>
Click me
You clicked {count} times
>
)
引入React中的 useState
通过调用 useState
声明一个 state
变量,该函数返回一对值,通过方括号进行数组解构赋值,我们定义两个命名变量,第一个解构变量是 state
变量名,第二个解构变量是对该 state
变量进行更新的函数
同类组件中的 state
变量一样,我们不能直接修改它,需要使用上述第二个解构变量进行间接修改它(如同类组件中的 setState
一样)
函数组件内没有 this
,我们在引用变量的时候,直接使用上述第一个命名解构变量即可,无需使用 this.Xxx
多个 state
变量可以进行多次进行声明和调用,与类组件中不同的是,对不同的 state
变量进行更新时,不再是合并,而是直接替换目标变量
2.7.2 Effect Hook 示例
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
You clicked {count} times
setCount(count + 1)}>
Click me
);
}
useEffect Hook
有两个参数,第一个参数是一个带有副作用的函数,在DOM更新之后会调用它,即 componentDidMount
、componentDidUpdate
生命周期钩子之后。第二个参数是内部 useState
和函数组件参数——即props
中若干变量组成的数组,如果省略第二个参数,则意味着每次更新渲染之后都会执行这个 useEffect Hook
。如果我们的 useEffect Hook
只依赖于部分变量的修改,可以在数组中订阅它。如果只想在挂载和卸载时执行,传入空数组 []
就好了。
有些 useEffect Hook
是需要被手动清除的。当订阅外部数据源,清除工作非常重要,可以防止引起内存泄露。WHY???当然是闭包。useEffect Hook
返回一个函数,React 将会在执行清除操作时调用它,相当于 componentWillUnmount
生命周期调用。
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Specify how to clean up after this effect:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
2.7.3 其他常用Hook
useCallback
返回一个 memoized
回调函数。
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
把内联回调函数及依赖项数组作为参数传入 useCallback
,它将返回该回调函数的 memoized
版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate
)的子组件时,它将非常有用。
useMemo
返回一个 memoized
值。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
把“创建”函数和依赖项数组作为参数传入 useMemo
,它仅会在某个依赖项改变时才重新计算 memoized
值。这种优化有助于避免在每次渲染时都进行高开销的计算。
useReducer
useRef
useImperativeHandle
useLayoutEffect
useDebugValue
自定义Hook
2.7.4 注意事项
只在最顶层使用Hook, 不要在循环、条件或嵌套函数中调用Hook
只在React函数中调用Hook,不要在普通的JavaScript函数只能怪调用Hook,可以在React的函数组件中调用或在自定义Hook中调用
第3章:React应用(基于React脚手架) 3.1 使用脚手架工具创建React应用 3.1.1 脚手架介绍 React官网推荐使用 Create React App
创建一个新的单页应用。如果是大型复杂的前端项目,我们也可以自己从0开始搭建——脚手架工具包含三大组件:package管理器、打包器、编译器,所以我们可以用npm + webpack + babel组件自己的工具。 脚手架工具可以给我们带来的便利包括:
拓展项目和组件的规模,以工程化的思维管理项目,模块化、组件化、工程化
包含了项目所需的基本配置
指定好了基础依赖
提供了一个最简单demo可以直接查看效果
清晰的文件管理目录
3.1.2 创建项目并启动 Create React App
会配置你的开发环境,以便使你能够使用最新的 JavaScript
特性,提供良好的开发体验,并为生产环境优化你的应用程序。
npx create-react-app my-app
cd my-app
npm start
3.1.3 理解项目结构 3.2 demo 第4章:开发中若干要点 4.1 组件间通信 4.1.1 方式一:通过props传递
共同的数据放在父组件上,特有的数据放在内部作为 state
维护
通过 props
可以传递一般数据和函数,一层一层传递
一般数据是由父组件传递给子组件,子组件进行数据读取,而函数经常被用来在子组件中调用并传递数据给父组件。
4.1.2 方式二:使用消息发布订阅机制 可以借助PubSub库来实现
4.1.3 方式三: Context Props
属性是自上而下传递的,但当一个属性是应用程序中许多组件都需要的时候,显式地通过组件树层层传递就显得繁琐,Context
提供了一个无需为每层组件手动添加 props
,就能在组件树间进行数据传递的方法。Context
设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。props
代码示例:
class App extends React.Component {
render() {
return ;
}
}
function Toolbar(props) {
// Toolbar 组件接受一个额外的“theme”属性,然后传递给 ThemedButton 组件。
// 如果应用中每一个单独的按钮都需要知道 theme 的值,这会是件很麻烦的事,
// 因为必须将这个值层层传递所有组件。
return (
);
}
class ThemedButton extends React.Component {
render() {
return ;
}
}
Context
代码示例
// 使用Context
// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
// 为当前的 theme 创建一个 context(“light”为默认值)。
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
// 无论多深,任何组件都能读取这个值。
// 在这个例子中,我们将 “dark” 作为当前的值传递下去。
return (
);
}
}
// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar() {
return (
);
}
class ThemedButton extends React.Component {
// 指定 contextType 读取当前的 theme context。
// React 会往上找到最近的 theme Provider,然后使用它的值。
// 在这个例子中,当前的 theme 值为 “dark”。
static contextType = ThemeContext;
render() {
return ;
}
}
注意事项:
Context
主要应用场景在于很多不同层级的组件需要访问同样一些的数据。请谨慎使用,因为这会使得组件的复用性变差。
4.1.4 方式四:Redux Redux提供一种全局的应用程序状态管理工具,可以像 vue
中的 vuex
一样方便地管理共享状态。后面会详细介绍。
4.2 异步请求 React本身只关注于界面,并不包含 ajax
请求,当需要前后台交互时,需要集成第三方 ajax
库。axios
和 fetch
是较为常用的 ajax
请求库:axios
: 封装了 XmlHttpRequest
对象的 ajax
的轻量级工具,支持 promise
,浏览器端和服务端均可使用fetch
: 是新版浏览器支持的原生函数,老版本浏览器并不支持,需要支持老版本的话可添加 fetch.js
,不再使用 XmlHttpRequest
对象提交 ajax
4.3 ES6及TypeScript ES6: 由于React类组件是采用 ES6 class
定义的,所以我们需要在一开始就配合 babel
使用。 当项目越来越大时,增加静态类型检查器可以在运行前识别某些类型错误。React中提供了 PropTypes
来限定 props
的输入类型,但其实在函数输入输出、 state
管理、接口统一规范等各处都需要进行静态类型检查。 建议在大型代码库中使用 Flow
或 TypeScript
来代替,尤其是 TypeScript
。
4.4 开源UI组件库
material-ui 国外的一款开源react UI组件库
ant-design 由国内蚂蚁金服打造的react UI组件库, 主要用于研发企业级中后台产品,同时提供移动端版本 ant-design-mobile
注意:在使用UI组件库时,尽量采用按需加载的方式打包组件,不要讲UI组件库一次性全盘导入,不利于缩小打包体积。
第5章:react-router 5.1 基本概念 5.1.1 react-router 与 react-router-dom react-router
是react的一个专门用于实现SPA应用的插件库,基于react的项目基本上都会用到此库,它实现了路由的核心功能。目前我们使用的是 react-router5
的版本。react-router-dom
则是基于 react-router
并加入了在浏览器运行环境下的一些功能,例如 Link
组件、BrowserRouter
和 HashRouter
组件。
5.1.2 路由分类 路由是一种映射关系,key
为路由路径, value
为 function/component
后台路由 服务器路由,value
是 function
,用于处理用户提交的请求并返回响应数据
前台路由 浏览器端路由,value
是 component
,当请求路由path匹配时,界面会更新显示对应的路由,没有发生http请求
5.1.3 路由组件分类
根组件:BrowserRouter
(history模式) 和 HashRouter
(hash模式)BrowserRouter
(history模式) 和 HashRouter
(hash模式)作为路由配置的最外层容器,是两种不同的模式,可根据需要选择。
路径匹配组件: Route
和 Switch
导航组件: Link
和 NavLink
5.2 history 库 history
库是 react-router
包的唯二依赖,另一个是react本身。这个库管理浏览器会话历史工具库,有三种模式:
browser history
, 基于 H5 History
Api 封装
hash history
基于 window.location.hash
封装
memory history
在no-DOM环境中适用,比如RN
history
对象提供了和 H5 History
Api非常相似的属性和方法
length
action
loction
( pathname | search | hash | state )
push(path, [state])
replace(path, [state])
go(n)
goBack()
goForward()
block(prompt)
5.3 react-router 相关API 5.3.1 路由组件 BrowserRouter 和 HashRouter BrowserRouter
(history模式) 和 HashRouter
(hash模式)作为路由配置的最外层容器,是两种不同的模式,可根据需要选择。
// history 模式:
class App extends Component {
render() {
return (
)
}
}
// hash 模式:
class App extends Component {
render() {
return (
)
}
}
5.3.2 路径匹配组件: Route 和 Switch
Route
: 用来控制路径对应显示的组件 参数:path
: 指定跳转路径exact
: 精确匹配路由component
: 路由对应组件render
: 通过写render函数返回具体的dom或组件location
: 与当前历史记录位置以外的位置相匹配,则此功能在路由过渡动效中非常有用sensitive
:是否区分路由大小写strict
: 是否配置路由后面的 '/'
Switch
: 渲染与该地址匹配的第一个子节点
或者
。类似于选项卡,只是匹配到第一个路由后,就不再继续匹配
注意上述的 path='/detail/:id'
,当路由为 /detail/1
时,若不加 exact
,则只会匹配渲染 path='/detail'
路由,添加只会,才会正确地渲染 path='/detail/:id'
路由。
5.3.3 导航组件: Link 和 NavLink Link
和 NavLink
都可以用来指定路由跳转, NavLink
的可选参数更多。 NavLink
可以看做 一个特殊版本的 Link
,当它与当前 URL 匹配时,为其渲染元素添加样式属性。
登录
登录
5.3.4 导航组件: Redirect
将导航到一个新的地址。即重定向。
上面,当访问路由 /
时,会直接重定向到 /home
。
常用语用户登录
class Center extends PureComponent {
render() {
const { loginStatus } = this.props;
if (loginStatus) {
return (
个人中心
)
} else {
return
}
}
}
5.3.5 其他
withRouter
函数withRouter
函数可以将一个非路由组件包裹为 withRouter
高阶路由组件,使这个非路由组件也能访问到当前路由的 match
, location
, history
对象。不论组件何时渲染, withRouter
总会将最新的 match
, location
, history
通过 props
传递给这个被封装的组件。
import React from "react";
import PropTypes from "prop-types";
import { withRouter } from "react-router";
// A simple component that shows the pathname of the current location
class ShowTheLocation extends React.Component {
static propTypes = {
match: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
history: PropTypes.object.isRequired
};
render() {
const { match, location, history } = this.props;
return You are now at {location.pathname}
;
}
}
// Create a new component that is "connected" (to borrow redux
// terminology) to the router.
const ShowTheLocationWithRouter = withRouter(ShowTheLocation);
match
对象match
对象包含了
对当前URL的匹配情况,包含以下属性:params
: 动态路由的 key-value
匹配对isExact
: 如果精准匹配上了,没有多余的结尾字符,为truepath
: path匹配表达式,
时有用url
: url 匹配串,
时有用
// 如果获取match对象
Route component as this.props.match
Route render as ({ match }) => ()
Route children as ({ match }) => ()
withRouter as this.props.match
matchPath as the return value
useRouteMatch as the return value
location 对象
{
key: 'ac3df4', // not with HashHistory!
pathname: '/somewhere',
search: '?some=search-string',
hash: '#howdy',
Redux 是专门用于做状态管理的JS库,并不是React的插件库,angular\vue中也可以使用。它可以集中式管理 React 应用的多个组件共享状态。当应用中多个组件需要共享并管理某些状态时,可以用到Redux。比如应用的主题theme管理、音乐类应用的当前歌曲列表管理等。 redux的工作流程如下: