0%

生命周期

X98__G_4L_~_VZ36_TXHEAG.png
上半部分视图渲染,下半部分事件通知
第一阶段:加载阶段
先是初始化数据constructor
然后进行页面初次渲染render
最后完成加载后发布did mount通知

第二阶段:更新阶段
先是更新数据渲染一次render
然后每更新一次每发布一次did update通知

第三阶段:卸载阶段
在组件卸载之前进行will unmount通知

具体运用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
constructor() {
super(...arguments);
this.state = {
user: [],
tempavatar: '',
};
}
componentDidMount() {
Fetch('/money/my/message', {}, 'GET')
.then(res => {
console.log(res.data);
if (res.data) {
this.setState({
user: res.data,
nickname: res.data.Nickname
})
}
})
}

在写接口时经常需要在页面加载时就获取数据,这是需要把接口写在componentDidMount() {}中

state更新

关于state,官方文档中写道“关于setState()你应该了解的三件事”:

(1)不要直接修改state
(2)state的更新可能是异步的
(3)state的更新会被合并

那么怎么理解这些?

(1) this.state.value = 1 这种写法是错误的

绝对不要直接修改 this.state ,这不仅是一种低效的做法,而且很有可能会被之后的操作替换。

(2)异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。

异步就相当于当客户端发送给服务端请求时,在等待服务端响应的时候,客户端可以做其他的事情,这样节约了时间,提高了效率。

注:异步虽然好,但是有些问题是要用同步用来解决,比如有些东西我们需要的是拿到返回的数据在进行操作的。这些是异步所无法解决的。

1
2
3
4
5
6
changeText() {
this.setState({
message: "新年快乐"
})
console.log(this.state.message); // Hello World
}

由此可见setState是异步的操作,我们并不能在执行完setState之后立马拿到最新的state的结果

Q:为什么要设计成异步?

setState设计为异步,可以显著的提升性能;

如果每次调用 setState都进行一次更新,那么意味着render函数会被频繁调用,界面重新渲染,这样效率是很低的;

最好的办法应该是获取到多个更新,之后进行批量更新;

如果同步更新了state,但是还没有执行render函数,那么state和props不能保持同步;

state和props不能保持一致性,会在开发中产生很多的问题;

Q:如何获取异步的结果,如何获取到更新后的值?

方式一:setState的回调

setState接受两个参数:第二个参数是一个回调函数,这个回调函数会在更新后会执行;

1
2
3
4
5
this.setState({
message: "新年快乐"
}, () => {
console.log(this.state.message);
})
方式二:在生命周期函数内获取
1
2
3
componentDidUpdate() {
console.log(this.state.message);
}

Q:setState一定是异步吗?

其实分成两种情况:

1、在组件生命周期或React合成事件中,setState是异步

2、在setTimeout或者原生dom事件中,setState是同步

验证一:在setTimeout中的更新:

1
2
3
4
5
6
7
8
9
changeText() {
// 将setState放入到定时器中
setTimeout(() => {
this.setState({
message: "新年快乐"
})
console.log(this.state.message); // 新年快乐
}, 0);
}

验证二:原生DOM事件:

1
2
3
4
5
6
7
8
componentDidMount() {
document.getElementById("btn").addEventListener("click", (e) => {
this.setState({
message: "新年快乐"
})
console.log(this.state.message);
})
}

(3)合并

1
2
3
4
5
function incrementMultiple() {
this.setState({count: this.state.count + 1});
this.setState({count: this.state.count + 1});
this.setState({count: this.state.count + 1});
}

结果this.state.count的值是1

1
2
3
4
this.setState({Age: '22'})
this.setState({Name: 'srtian'})
// 等价于
this.setState({Age: '22', Name: 'srtian})

会将数据进行合并,且通过setState对其中一个进行更新,另一个不受影响

React的官方文档有提到过这么一句话:
状态更新会合并(也就是说多次setstate函数调用产生的效果会合并)。

原理:
在react中,setState通过一个队列机制实现state的更新。当执行setState时,会把需要更新的state合并后放入状态队列,而不会立刻更新this.state,当进入组件可更新状态时,这个队列机制就会高效的批量的更新state。

加深对state更新的理解:想想下面这段代码会输出什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Example extends React.Component{
constructor(){
super(...arguments)
this.state = {
count: 0
};
}
componentDidMount(){
// a
this.setState({count: this.state.count + 1});
console.log('1:' + this.state.count)
// b
this.setState({count: this.state.count + 1});
console.log('2:' + this.state.count)
setTimeout(() => {
// c
this.setState({count: this.state.count + 1});
console.log('3:' + this.state.count)
}, 0)
// d
this.setState(preState => ({ count: preState.count + 1 }), () => {
console.log('4:' + this.state.count)
})
console.log('5:' + this.state.count)
// e
this.setState(preState => ({ count: preState.count + 1 }))
console.log('6:' + this.state.count)
}
}

参考链接:https://blog.csdn.net/wuyxinu/article/details/113902057

React渲染

1、HTML中渲染
2、参数方式渲染
3、function方式渲染
4、组件方式渲染(详细讲解)
举例说明:获取用户评论并渲染到页面
构造一个组件(写在Components文件夹中),用export default暴露出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export default class Index extends Component {
render() {
const {id,time,content}=this.props
return (
<View className='box_comment'>
<View className='upper'>
<View className='xuehao'>学号:{id}</View>
<View className='pushTime'>{time} </View>
</View>
<View className='lower'>{content}</View>
</View>
)
}
}

通过Comment组件将获取到的评论渲染出来

1
2
3
4
5
6
<View className='h'>精彩评论</View>
{comments.map((comment) => {
return (
<Comment id={comment.ID} time={comment.Givetime} content={comment.Comment} key='comment' />
)
})}

参考链接:https://blog.csdn.net/qq_39905409/article/details/104001438

图片合成实例——国庆头像H5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
function drawCanvasImage() {
let width = 500
let height = 500
downloadRef.current.width = width
downloadRef.current.height = height
let myimage = new Image() //创建图片对象
if(!imgURL){
alert('请选择图片')
return
}
myimage.src = imgURL //设置绘制图像路径
let background = new Image()
if(!kuang){
alert('请选择头像框')
return
}
background.setAttribute("crossOrigin",'Anonymous')//解决跨域问题,画布污染问题
background.src = kuang
if(downloadRef.current.getContext){
let ctx = downloadRef.current.getContext('2d')//获取绘图2D环境
myimage.onload = function(){//图片加载完再去控制
ctx.drawImage(myimage,0,0,width,height)//在画布上根据坐标绘制
background.onload = function() {
ctx.drawImage(background,0,0,width,height)
downloadImage()
}

}
}
}

const downloadImage = ()=>{
if(window.innerWidth < 1000 ) {
const url = downloadRef.current.toDataURL()
setDownloadURL(url)
setIsDownload(true)
setIsTip(true)
setTimeout(() => {
setIsTip(false);
}, 3000);
setIsChoose(false)
} else {
const url = downloadRef.current.toDataURL();
const a = document.createElement("a"); // 生成一个a元素
const event = new MouseEvent("click"); // 创建一个单击事件
a.download = name || "avatar"; // 设置图片名称
a.href = url; // 将生成的URL设置为a.href属性
a.dispatchEvent(event); // 触发a的单击事件
}
}
1
<canvas ref={downloadRef} id='canvas'></canvas>

useRef()

1、返回一个可变的 ref 对象,该对象只有个 current 属性,初始值为传入的参数( initialValue )。
2、返回的 ref 对象在组件的整个生命周期内保持不变
3、当更新 current 值时并不会重新渲染,这是与 useState 不同的地方

※使用useRef可以保证实时获取到最新的值

toDataURL()

1、toDataURL()方法是什么?

toDataURL()是canvas对象的一种方法,用于将canvas对象转换为base64位编码;

2、利用canvas的toDataURL()方法如何将图片转换为base64编码?

通过将图片绘制到canvas中,然后将canvas对象转换为base64编码,从而实现图片转为base64编码;

3、将图片转换为base64位编码有什么好处?

1)将图片转换为base64位编码后,图片会跟随代码(html、css、js)一起请求加载,不会再单独进行请求加载;

2)可以防止由于图片路径错误导致图片加载失败的问题;

4、toDataURL()方法的两个参数:toDataURL(type, encoderOptions)

1)type指定转换为base64编码后图片的格式,如:image/png、image/jpeg、image/webp等等,默认为image/png格式;

2)encoderOptions用于设置转换为base64编码后图片的质量,取值范围为0-1,超出取值范围用默认值0.92代替;

a标签法本地下载图片

步骤:
1、将 canvas 元素的数据通过原生 api 转换成 base64 编码的图片格式
2、利用 a 标签设置 download 属性可以将 href 链接元素下载,我们将 a 标签的 href 属性值设置为上一步获得的 base64 格式字符串
3、构造一个单击事件并通过 api 触发 a 标签的 click 事件完成下载

优缺点:
优点:只使用浏览器提供的原生 API 就能实现我们的需求。
缺点:
1)无法被异步代码包裹,也就是包含 Ajax 请求的情况下代码不生效。
2)对于分辨率过高的 canvas, 我们生成的 dataURL 过长,超过浏览器限制,可能会导致无法顺利下载。

解决办法:可采用blob方法,感兴趣可以百度学习一下^_^

canvas补充

详细学习canvas可参考https://www.runoob.com/w3cnote/html5-canvas-intro.html
学习2d上下文可参考https://blog.csdn.net/qq_41218152/article/details/82664178

ahooks简介

ahooks是由蚂蚁 umi 团队、淘系 ice 团队以及阿里体育团队共同建设的 React Hooks 工具库。
它正致力成为和 antd/fusion 一样的 React 基础设施,帮助开发者在逻辑层面省去大量的重复工作。

核心useRequest的基础用法

默认请求

1
const { data, error, loading } = useRequest(service);

第一个参数是一个异步函数,在组件初始化时,会自动执行该异步函数。同时自动管理该异步函数的 loading , data , error 等状态。

手动触发

1
2
3
4
5
6
7
const { loading, run, runAsync } = useRequest(service, {
manual: true
});

<button onClick={run} disabled={loading}>
{loading ? 'Loading' : 'Edit'}
</button>

设置了 options.manual = true,则 useRequest 不会默认执行,需要通过 run 或者 runAsync 来触发执行。

Q:run 与 runAsync 的区别

生命周期
在异步函数的不同阶段做一些处理


  • onBefore:请求之前触发

  • onSuccess:请求成功触发

  • onError:请求失败触发

  • onFinally:请求完成触发

刷新
使用refresh代替run
refresh 与 refreshAsync 的区别与run一致

立即变更数据
mutate支持立即修改返回的数据,用法与setState一致

取消响应
cancel函数用于忽略当前promise返回的数据和错误
注意:调用cancel函数并不会取消promise的执行

useRequest的具体应用

1、Loading Delay

1
2
3
4
5
const { loading, data } = useRequest(getUsername, {
loadingDelay: 300
});

return <div>{ loading ? 'Loading...' : data }</div>

若在300ms内展示,则不显示loading···

2、轮询

1
2
3
const { data, run, cancel } = useRequest(getUsername, {
pollingInterval: 3000,
});

每隔3000ms自动请求一次getUsername
可通过cancel停止、run启动

3、ready

1
2
3
4
5
6
const [ready, { toggle }] = useToggle(false);

const { data, loading } = useRequest(getUsername, {
ready,
//manual: true 手动模式
});

只有当ready为 true 时,才会执行

4、依赖刷新

1
2
3
4
5
const [userId, setUserId] = useState('1');

const { data, run } = useRequest(() => getUserSchool(userId), {
refreshDeps: [userId],
});

1
2
3
4
5
const { data, refresh } = useRequest(() => getUserSchool(userId));

useEffect(() => {
refresh();
}, [userId]);

切换userId后立即刷新

5、屏幕聚焦重新请求

1
2
3
const { data } = useRequest(getUsername, {
refreshOnWindowFocus: true,
});

再次进入当前页面,如果和上一次请求间隔大于 5000ms 则会重新请求一次

6、防抖

1
2
3
4
const { data, run } = useRequest(getUsername, {
debounceWait: 300,
manual: true
});

频繁触发run,只会在最后一次触发结束后等待 300ms 执行。

7、节流

1
2
3
4
const { data, run } = useRequest(getUsername, {
throttleWait: 300,
manual: true
});

频繁触发run,只会每隔 300ms 执行一次。

8、缓存
cacheKey:将当前请求成功的数据缓存起来。
staleTime: 设置数据保持新鲜时间,在该时间内不会重新发起请求。
cacheTime: 设置数据缓存时间,超过该时间自动清空该条缓存数据。

Redux简介和使用场景
redux:一个专门做状态管理的JS库,可以集中式管理react应用中多个组件共享的状态
使用的两种情况:
(1)某个组件的状态,需要让其他组件可以随时拿到(共享)
(2)一个组件需要改变另一个组建的状态(通信)

Redux的工作流程
FF25D33DCCF76F93DC85BAA5A3D44F54.png

我理解的:
React Components提要求告诉Action Creators,然后Action Creators把type和data告诉Store,Store知道后再把东西传给Reducers,Reducers加工完把新的state返给Store。

Action Creators:核心是把获取到的东西包装成对象,也可以不要它
Reducers:不仅能够加工对象,也可以进行初始化

如果比作一家餐厅的话,React Components是顾客,Store是老板,Action Creators是服务员,Reducers是后厨团队。

Redux三个核心概念
一、action
1.动作的对象
2.包含两个属性
type:标识属性,值为字符串,必要属性
data:数据属性,值类型任意,可选属性
3.例子:{type:’ADD_STUDENT’,data:{name:’tom’,age:18}}

二、reducer
1.用于初始化状态、加工状态
2.加工时,根据旧的state和action,产生新的state的纯函数

三、store
1.将state、action、reducer联系在一起
2.如何得到此对象?
(1)import {createStore} from ‘redux’
(2)import reducer from ‘./reducers’
(3)const store = createStore(reducer)
3.此对象的功能
(1)getState():得到state
(2)dispatch(action):分发action,触发reducer调用,产生新的state
(3)subscribe(listener):注册监听,当产生了新的state时,自动调用

Redux三个API
第一个API:getState

第二个API:dispatch
组件里要执行的函数需要去通知store自己要执行,这就需要用dispatch(注意要告诉自己的type和data)
第三个API:subscribe
Redux里状态的改变不会引起页面更新,所以需要监测redux里状态的变化,而监测就需要用subscribe

Redux实际应用
App.jsx文件

1
2
3
4
5
6
7
8
9
10
11
import React, { Component } from 'react'
import Count from './Components/Count'

export default class App extends Component {
render() {
return (
<div><Count/></div>
)
}
}

index.js入口文件

1
2
3
4
5
6
7
8
9
10
11
import React from "react"
import ReactDOM from "react-dom"
import App from "./app"
import store from "./redux/store"

ReactDOM.render(<App/>,document.getElementById("root"))

store.subscribe(()=>{
ReactDOM.render(<App/>,document.getElementById("root"))
})
//这样就不用在每个组件的componentDidMount() {}中都写

store.js文件

1
2
3
4
//此文件专门用于暴露一个store对象
import {createStore} from 'redux'
import countReducer from './countReducer'
export default createStore(countReducer)

count_reducer.js文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const initState = 0
export default function countReducer(preState=initState, action){
const {type,data} = action
switch(type){
case 'increment':
return preState + data
break;
case 'decrement':
return preState - data
break;
default:
return preState
}
}

组件中的count.js文件
(这里隐藏着一个小问题哦……)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React,{ Component} from 'react'
import store from '../../redux/store'

export default class Count extends Component {
increment=()=>{
const {value} = this.selectNumber
store.dispatch({type:'increment',data:value*1})
}
decrement=()=>{
const {value} = this.selectNumber
store.dispatch({type:'decrement',data:value*1})
}
render() {
return (
<div>
<h1>当前求和为:{store.getState()}</h1>
<select ref={c => this.selectNumber = c}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>&nbsp;
<button onClick={this.increment}>+</button>&nbsp;
<button onClick={this.decrement}>-</button>
</div>
)

}

}

木有了

Tab标签页

image.png
大致就是这种效果
最开始尝试过去找原生的成型代码,但是因为插件一系列问题没有成功,于是开始自己写……

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
state = {
currentBoxId: 'stepone' //当前显示的View的id
}
changeBox(e) {
let currentFlag = e.currentTarget.id;
switch (currentFlag) {
case 'steponeNext':
this.setState({
currentBoxId: 'stepone'
})
break;
case 'steptwoNext':
this.setState({
currentBoxId: 'steptwo'
})
break;
case 'stepthreeNext':
this.setState({
currentBoxId: 'stepthree'
})
break;
case 'stepfourNext':
this.setState({
currentBoxId: 'stepfour'
})
break;
}
}

正文部分
image.png

这其实是受到写赚圈圈时上一步下一步页面的启发(复制粘贴过来删除了一部分,但这个命名就懒得换了)

好的,让我们看看上一步下一步的写法

image.png

原理大致都是一样的,当点击不同的按钮,改变状态,跳转到不同的地方

Select选择框

image.png
html中有select写法,可以直接写出来下拉选择框。小程序原生虽然有Picker写法,但是效果不是这样的。

正文部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<View className='picker'>
<View className='select teshu' onClick={this.openone.bind(this)}>
<Text>时长</Text>
<Image src='../images/down.png' alt=''></Image>
<View className={this.state.show? "show" : "hidden"}>
<View>&nbsp;&nbsp;2-4h</View>
<View>&nbsp;&nbsp;半天</View>
<View>&nbsp;&nbsp;全天</View>
</View>
</View>
<View className='select teshu' onClick={this.opentwo.bind(this)}>
<Text>难度</Text>
<Image src='../images/down.png' alt=''></Image>
<View className={this.state.modal? "show" : "hidden"}>
<View>&nbsp;&nbsp;简单</View>
<View>&nbsp;&nbsp;中等</View>
<View>&nbsp;&nbsp;困难</View>
</View>
</View>
<View className='select teshu' onClick={this.openthree.bind(this)}>
<Text>人数</Text>
<Image src='../images/down.png' alt=''></Image>
<View className={this.state.showmodal? "show" : "hidden"}>
<View>&nbsp;&nbsp;4</View>
<View>&nbsp;&nbsp;6</View>
<View>&nbsp;&nbsp;8</View>
<View>&nbsp;&nbsp;10</View>
</View>
</View>
</View>

渲染部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
state = { show: false , modal: false , showmodal: false}

componentWillMount() { }

componentDidMount() { }

componentWillUnmount() { }

componentDidShow() { }

componentDidHide() { }

openone () {
const {show} = this.state
switch (show) {
case false:
this.setState({
show: true
})
break;
case true:
this.setState({
show: false
})
break;
}
}
opentwo () {
const {modal} = this.state
switch (modal) {
case false:
this.setState({
modal: true
})
break;
case true:
this.setState({
modal: false
})
break;
}
}
openthree () {
const {showmodal} = this.state
switch (showmodal) {
case false:
this.setState({
showmodal: true
})
break;
case true:
this.setState({
showmodal: false
})
break;
}
}

css部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
.picker {
display: flex;
}
.select {
width: 200px;
height: 60px;
line-height: 60px;
background-color: #fff;
margin-left: 20px;
padding-left: 7px;
}
.teshu {
position: relative;
}
.select Image {
float: right;
margin-top: 7px;
width: 50px;
height: 50px;
}
.show {
position: absolute;
top: 60px;
left: 0;
width: 200px;
box-shadow: 0 2px 10px rgba(0, 0, 0,0.1);
}
.show View {
background-color: #fff;
}
.hidden {
display: none;
}

用了showmodal的方法,用的时候显示,不用的时候隐藏(这种方法也可以使用在弹窗之类的地方)

原型之间的关系是继承的基础
首先最重要的一点是Javascript中所有的对象都是Object的实例,并继承Object.prototype的属性和方法.(Object.prototype最高,再往上找是Null了)

(用console.dir看原型,可进行检验)

重点:prototype与__proto__的区别
在对象创建时会有一些预定义的属性,其中定义函数时,这个预定义属性就是prototype,定义普通对象时,就会生成一个__proto__,,这个__proto__指向的是这个对象的构造函数的prototype。
比如说,构造了一个函数B,b是实例(b是对象,只有__proto__属性),那么b.proto==B.prototype答案是true
注:把构造函数当成对象叫时用__proto__;而对构造函数中的多个对象时,用prototype。
User.prototype.proto=Object.prototype =>true
User.prototype.constructor=User =>true

in不只检测对象还检测原型链;而hasOwnProperty只检测当前对象
(key用法:key打印属性大名,a[key]打印内容)

this始终指向调用其所在属性的对象

继承
原型的继承而不是改变构造函数的原型
常见六种继承方式
1.原型链继承
重点:让新实例的原型等于父类的实例
CHILD.prototype = new PARENT();
CHILD.prototype.constructor = CHILD;
setPrototypeOf(a,b) 人为设置(getPrototypeOf获取)
b是a的原型(父级),a能够继承b的所有属性
1.png
2.借用构造函数继承
重点:使用call()和apply()将父类构造函数引入子类函数
apply:应用某一对象的一个方法,用另一个对象替换当前对象。
call:调用一个对象的一个方法,以另一个对象替换当前对象。
注:apply最多两个参数,要是很多个数据的话,需要写在[]数组中
call正常,,的写就行
2.png
3.组合继承(原型链+构造函数)(最常用)
重点:结合了两种模式的优点,传参和复用
3.png
4.原型式继承–Object.create
重点:用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象。
var B = Object.create(A)以A为原型创建了B
这种方法只接收两种参数
(1)用作新对象原型的对象
(2)为新对象定义额外属性的对象
Object.create(OBJ)创建一个空对象,让空对象的__proto__指向OBJ
重定向的同时不要忘记写B.prototype.constructor=B
4.png
5.寄生式继承
重点:给原型式继承外面套了一层
5.png
6.寄生组合式继承(常用)
重点:修复了组合继承的问题
6.png
7.ES6中的class继承
class CHILD extends PARENT{}{
//如果写constructor,则constructor中的第一句话必须是super()
super()=>A.call(this)
}
1.png

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

第一次写博客纪念一下,最近好忙啊