该笔记为学习B站尚硅谷React课程所记录。部分截图来源于视频。
React特点
- 组件化
- 虚拟DOM
- 声明式
Babel
- ES6 ==> ES5
- jsx==>js
- tsx==>ts
为什么使用jsx而不是js
创建虚拟DOM时,jsx可以直接写标签,而js只能用React.creatElement(),当有标签嵌套时,js将一直嵌套,非常复杂。jsx相当于js语法糖。
jsx:
js:
JSX语法
1. 虚拟DOM
创建虚拟DOM时,直接使用html标签而不是字符串,不能加引号
2. { }
引入js表达式时,需要用{ }括起来
<p className="count-number">
In {props.title} page, You clicked <span>{count}</span> times
</p>
3. className
在引入css样式时,不使用class,而是className
<div className="hook-example">
<div>Hook Example</div>
<p className="count-number">
In {props.title} page, You clicked <span>{count}</span> times
</p>
<button
onClick={() => {
setCount(count + 1);
}}
>
Click me
</button>
</div>
- 内联style样式{{ }}
<span style={{color:'red'}}></span>
5. 标签首字母
小写,找html对应标签,没有则报错;
大写,react渲染对应标签,没有则报错
组件
函数式组件
适用于简单组件
类式组件
适用于复杂组件(使用state的组件)
React.Component属性
state
setState
修改状态需要使用setState(),而不能直接对this.state赋值;对state的更新是合并,而非替换;每次更新state都会调用一次render()
this
容易犯错
class Weather extends React.Component{
constructor(props){
super(props);
this.state = {isHot:true};
}
render(){
return <span onClick={this.changeWeather}>{this.state.isHot ? Hot:Cold}</span>;
}
changeWeather(){
const isHot = this.state.isHot;
this.setState({isHot:!isHot});
}
}
问题:点击文字并不会改变
this:undefined
这是因为将函数changeWeather赋值给了onClick,并不是对象在调用这个函数;并且因为使用strict模式,this类型是undefined,而不是window
解决办法1:
this.changeWeather = this.changeWeather.bind(this);
解决方案2:
changeWeather = ()=>{
const isHot = this.state.isHot;
this.setState({isHot:!isHot});
}
箭头函数中的this默认指向其所在函数作用域链
Props
用于存储外部传进来的数据,从而动态渲染页面;props是readonly类型
展开运算符…
利用prefix…可以将一个数组展开
let arr = [1,2,3,4,5]
console.log(...arr)
> 1, 2, 3, 4, 5
还可以合并两个数组
let arr1 = [1,2,3,4,5]
let arr2 = [6,7,8,9]
console.log(...arr1, ...arr2)
> 1, 2, 3, 4, 5, 6, 7, 8, 9
而对于对象来说,…可以将对象进行深拷贝
let person = new Person("Mark")
let referPerson = person
let clonePerson = {...person}
person.name = "Tom"
console.log(referPerson.name)
console.log(clonePerson.name)
> Tom
> Mark
也可以对对象进行扩展
let person = new Person("Mark")
let extendPerson = {...person, sex:'male'}
console.log(extendPerson)
>{name:'Mark', sex:'male'}
在React当中,当需要传递的数据信息很多时,我们往往将这些数据封装成一个对象来进行传递,并且在传递的时候我们采用这样的方式:
<MyComponent person={...person} />
这里采用的是{ }的方式,但并不是将person拷贝一份,因为这里的{ }是jsx中的取值运算符;因为在React+Babel中,对象是可以展开的,所以这里是将person展开,并不是复制,但仅限于在标签中使用
props类型限制
static prototype = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
}
注意:如果要限制类型为函数,使用PropTypes.func
,而不是PropTypes.function
props默认值设置
static defaultProps = {
age: 18,
sex: 'male'
}
函数式组件的props
通过函数参数传递props
function Weather(props){
return (
<ul>
<li>this.props.name</li>
<li>this.props.sex</li>
</ul>
)
}
ReactDOM.render(<Weather name='Mark' sex='male'/>, document.getElementById('root'))
函数式组件使用state
和refs
可以通过Hooks
实现
Refs
javascript
中,我们通常使用document.getElementById
来引用某个DOM
节点;而在React
中,我们使用Refs
完成类似的功能
字符串Refs
<input ref="myInput"/>
const input = this.refs.myInput
但是这种方式存在效率问题,官方已经不推荐使用
回调Refs
内联回调函数
render() {
return (
<div>
<input
type="text"
ref={element => {this.textInput = element;}}
/>
</div>
);
}
回调次数问题
createRef
勿过度使用 Refs
你可能首先会想到使用
refs
在你的app
中“让事情发生”。如果是这种情况,请花一点时间,认真再考虑一下state
属性应该被安排在哪个组件层中。通常你会想明白,让更高的组件层级拥有这个state
,是更恰当的。查看 状态提升 以获取更多有关示例。
将 DOM Refs 暴露给父组件 (Ref转发)
事件处理
-
React
中的事件处理函数名都为onXxx
,而原生组件的事件函数名为onxxx
React对原生DOM事件进行了自定义,将事件委托给组件的最外层元素
-
event.target
可以得到发生事件的DOM
元素
非受控组件
采用refs
对节点进行操作
受控组件
通过事件处理将数据保存到state
,通过state
进行数据渲染
高阶函数
- 函数的参数是一个函数
- 函数的返回值是一个参数
常见高阶函数:Promise
、setTimeout
、arr.map
函数的柯里化
Curry:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数
const sum = (a)=>{
return (b)=>{
return (c)=>{
return a+b+c
}
}
}
const res = sum(1)(2)(3)
console.log(res)
>
6
生命周期
挂载与卸载
mount
将一个组件挂载到一个容器中
ReactDOM.render(<Demo />, document.getElementById('root'))
unmount
将一个组件从容器中卸载,例如:删掉点击某个按钮关闭某个弹窗
ReactDOM.unmountComponentAtNode(document.getElementById('root'))
生命周期流程(旧)
componentWillMount
- 组件挂载之前执行一次
- 应用
- 开启定时器
- 发送网络请求
- 订阅消息
componentDidMount
- 组件挂载之后执行一次
- 应用
- 关闭计时器
- 取消订阅
render
- 组件挂载时调用一次
- 组件每次更新时调用一次
setState
更新组件状态forceUpdate
强制更新组件- 父组件
render
父组件更新组件
componentWillUnmount
- 组件卸载之前执行一次
shouldComponentUpdate
- 组件
state
更新后执行一次 - 默认
return true
return false
则不沿流程执行下去
componentWillUpdate
- 组件更新之前执行一次
componentDidUpdate
- 组件更新之后执行一次
componentWillReceiveProps
- 当父组件的
state
被当作子组件的props
传递时,父组件更新时,子组件会再次接收props
,该函数在子组件接收props
前调用一次 - 挂载时并不会调用
render(){
return (
<div>
<Son name={this.state.name}/>
</div>
)
}
生命周期流程(新)
改变:
-
移除了
componentWillMount
、componentWillUpdate
、componentWillReceiveProps
-
新增了
getDerivedStateFromProps
、getSnapshotBeforeUpdate
Diffing算法
状态数据发生变化时,react
根据新数据生成新的虚拟DOM
,与旧DOM
进行Diffing
比较,比较的最小粒度为标签
- 旧虚拟
DOM
中找到与新虚拟DOM
相同的key
- 内容不同,生成新
DOM
节点替换旧的 - 内容相同,不替换
- 内容不同,生成新
- 旧虚拟
DOM
中没有相同的key
- 生成新
DOM
渲染到页面
- 生成新
Key
唯一标识某个组件
index
用index
赋值给key
,非顺序操作元素时:
- 每个元素的
index
都将改变,从而导致对应的key
发生改变,导致diffing
算法在比较时将重新渲染所有元素对应的组件,效率大大降低 - 当该组件存在子组件、且子组件状态不发生变化时,
diffing
算法会保留子组件DOM
节点,重新渲染父组件DOM
节点,导致父子组件匹配错误
因此,如果列表元素存在唯一标识,最好用该唯一标识作为key
create-react-app
核心技术栈:
react
es6
webpack
eslint
创建项目并启动
-
安装create-react-app
npm install -g create-react-app
-
创建项目
cd desktop
create-react-app react-hello
-
启动项目
cd react-hello
npm start
项目文件结构
react-hello
├─node_modules 依赖
├─src
│ ├─App.css 组件App样式
│ ├─App.js 组件App
│ ├─App.test.js 组件App测试文件
│ ├─index.css 主页样式
│ ├─index.js 主页
│ ├─logo.svg react logo
│ ├─reportWebVitals.js 网页性能配置
│ └─setupTests.js 测试配置文件
└─public 静态资源
│ ├─favicon.ico 标签图标
│ ├─index.html 主页面
│ ├─logo192.png 图片size=192
│ ├─logo512.png 图片size=512
│ ├─manifest.json 加壳配置文件
│ └─robots.txt 爬虫配置文件
├─package.json 项目配置文件
└─yarn.lock 锁定版本号
界面组件化
- 拆分界面,按照合适的粒度划分组件
- 实现静态组件
- 实现动态组件
React Ajax
react
只关注界面,没有实现ajax
ajax
是必要的,与后台传送json
数据- 集成第三方
ajax
库
ajax库
- jQuery
jQuery
太重,性能不高;react
中的虚拟DOM
就是为了优化性能 - axios
轻量级
封装XmlHttpRequest
请求的ajax
promise
风格
能在浏览器端与node
服务器端使用
配置代理
消息订阅与发布
PubSubjs
react-router
-
SPA (Single Page Application)
- 整个应用只有一个完整页面,多个组件
- 点击不刷新页面,制作局部更新
- 数据通过Ajax请求获取,在前端异步展现
-
路由
- 路由是一个映射关系key-value
- key是一个路径,value是一个组件
安装react-router-dom
命令行输入yarn add react-router-dom
基本用法
Router
BrowserRouter
HashRouter
Route
Link
路由组件
- 写法
- 存放位置
- props
其他组件
NavLink
this.props.children
Switch
默认情况下,匹配路由将比较所有注册的项,这将带来两个问题:
- 多个组件注册同一个路由,将同时显示多个组件
- 注册的组件数量大,效率低下
在外面包裹Switch组件标签可以使得匹配第一个路由后停止匹配
Redirect
模糊匹配与严格匹配
嵌套路由
params参数
search参数
querystring
state参数
replace以及push
编程式路由
withRouter
加工一般组件,添加路由组件的三个特有属性history
、location
、match
import {withRouter} from 'react-router-dom'
...
export default withRouter(Component)
React-UI组件库
Ant-Design
安装Ant-Design
引入样式
按需引入样式
自定义主题
Redux
Mobx
LazyLoad
Hooks
Axios
json-server
https://github.com/typicode/json-server 快速搭建REST API
的工具包
安装与启动
-
安装
JSON Server
npm install -g json-server
-
创建一个
db.json
文件, 用下面的数据{ "posts": [ { "id": 1, "title": "json-server", "author": "typicode" } ], "comments": [ { "id": 1, "body": "some comment", "postId": 1 } ], "profile": { "name": "typicode" } }
-
启动
JSON Server
json-server --watch db.json
安装 Axios
基本用法
请求配置信息
{
url: '/user',
method: 'get', // default
baseURL: 'https://some-domain.com/api/',
transformRequest: [function (data, headers) {
return data;
}],
transformResponse: [function (data) {
return data;
}],
headers: {'X-Requested-With': 'XMLHttpRequest'},
params: {
ID: 12345
},
paramsSerializer: function (params) {
return Qs.stringify(params, {arrayFormat: 'brackets'})
},
data: {
firstName: 'Fred'
},
data: 'Country=Brasil&City=Belo Horizonte',
timeout: 1000, // default is `0` (no timeout)
withCredentials: false, // default
adapter: function (config) {
},
auth: {
username: 'janedoe',
password: 's00pers3cret'
},
responseType: 'json', // default
responseEncoding: 'utf8', // default
xsrfCookieName: 'XSRF-TOKEN', // default
xsrfHeaderName: 'X-XSRF-TOKEN', // default
onUploadProgress: function (progressEvent) {
// Do whatever you want with the native progress event
},
onDownloadProgress: function (progressEvent) {
// Do whatever you want with the native progress event
},
maxContentLength: 2000,
maxBodyLength: 2000,
validateStatus: function (status) {
return status >= 200 && status < 300; // default
},
maxRedirects: 5, // default
socketPath: null, // default
httpAgent: new http.Agent({ keepAlive: true }),
httpsAgent: new https.Agent({ keepAlive: true }),
proxy: {
protocol: 'https',
host: '127.0.0.1',
port: 9000,
auth: {
username: 'mikeymike',
password: 'rapunz3l'
}
},
cancelToken: new CancelToken(function (cancel) {
}),
signal: new AbortController().signal,
decompress: true // default
insecureHTTPParser: undefined // default
transitional: {
silentJSONParsing: true,
forcedJSONParsing: true,
clarifyTimeoutError: false,
}
}
默认配置
axios.default.method='GET'
创建实例对象与发送请求
Axios拦截器
Promise
Promise
是ES6
提出的对于异步编程的新解决方案(旧方案为回调函数)Promise
是一个构造函数promise
对象用来封装一个异步操作,并获取操作的状态值
异步编程
-
fs
文件操作require('fs').readFile('./index.html', (err, data)=>{})
-
数据库操作
-
Ajax
$.get('/server', (data)=>{})
-
定时器
setTimeout(()=>{}, 2000)
Promise特点
- 支持链式调用,避免回调地狱
- 指定回调函数的方式更灵活
quickstart
class PromiseTest extends React.Component{
handleClick(){
const p = new Promise((resolve, reject)=>{
setTimeout(()=>{
let num = 1
if(num>0){
resolve(num)
}
else{
reject()
}
}, 1000)
})
p.then(()=>{
alert('more than 0')
}, ()=>{
alert('less than 0')
})
}
render(){
return (
<button onClick={this.handleClick}>点击</button>
)
}
}
util.promisify
PromiseState
pending
resolved
/fullfilled
rejected
若成功;pending
==> resolved
;若失败,pending
==> rejected
PromiseResult
value
reason
若成功;solve(value)
;若失败,reject(reason)
. 通过调用promise.then()
、promise.catch()
对result
进行处理
Promise Functions
-
Promise.resolve()
参数为非promise
对象,返回一个成功promise
对象,PromiseResult
为参数
参数为promise
对象,根据该对象执行器的返回结果判断返回成功还是失败 -
Promise.reject()
无论参数是否为promise
对象,都返回失败的promise
对象 -
Promise.all()
参数为promises
,一个promise
对象数组,只有数组中所有promise
都是成功的,才返回成功promise
,result
为所有成功对象的result
;否则返回失败promise
,result
为所有失败对象的result
-
Promise.race()
参数为promises
,一个promise
对象数组,最先改变状态的promise
对象的结果为最终结果Tips:promise中的excutor是同步执行的
改变promise状态
pending
==>resolved
调用resolve()
pending
==>rejected
调用reject()
、throw
异常
常见问题
-
给同一个promise指定多个成功/失败回调函数,都会执行吗?
只要promise的pending状态发生了改变,全部回调函数都会执行;状态未改变则不执行
-
先改变状态还是先指定回调函数?何时能拿到结果数据?
都有可能。 如果promise中执行的是同步任务,或者then()延迟调用,则先改变状态; 如果执行的是异步任务,则先指定回调函数。 无论是先改变状态还是先指定回调函数,都是在状态改变后拿到结果。
-
then()返回的promise对象状态是成功还是失败?
三种情况: 抛出异常,失败 return一个非promise对象,成功且结果为返回值 return一个promise对象,取决于该返回promise对象的状态
异常穿透
在promise
的链式调用中,无论哪个节点出现异常,只需要在最后的promise
使用catch()
就能捕获到异常
中断promise链式调用
返回一个pending
状态的promise
对象
Async 函数
返回结果是一个promise
对象,PromiseState
以及PromiseResult
情况如下:
- 抛出异常,
throw 'message'
,{PromiseState:rejected,PromiseResult:message}
- 返回一个非
promise
对象,return 123
,{PromiseState:fullfilled,PromiseResult:123}
;没有返回值,{PromiseState:fullfilled,PromiseResult:undefined}
- 返回一个
promise
对象,取决于该返回promise
对象的PromiseState
和PromiseResult
Await 表达式
await
一定要在async
函数中使用;但async
函数中可以没有await
await
后面的变量是一个非promise
对象,返回该变量值await
后面的变量是一个状态成功的promise
对象,返回该对象PromiseResult
await
后面的变量是一个状态失败的promise
对象,通过try-catch
捕获失败对象的PromiseResult
> Error
async await 简化代码
未使用promise
使用promise