0%

什么是内存泄漏

内存泄漏是在计算机科学中,由于疏忽或错误造成程序未能释放已经不再使用的内存。

并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。程序的运行需要内存,只要程序提出要求,操作系统或者运行时
就必须供给内存,对于持续运行的服务进程,必须及时释放不再用到的内存,否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃。

垃圾回收机制

js具有自动垃圾回收机制,执行环境负责管理代码执行过程中使用的内存。垃圾收集器会周期性找出那些不继续使用的变量,然后将其释放。

垃圾回收机制主要通过两种方式实现: 标记清除和引用计数

标记清除

js中最常用的垃圾回收机制,当变量进入执行环境时, 就标记这个变量为“进入环境’’,进入环境的变量所占用的内存就不能释放,当变量离开环境时,则将其标记为”离开环境“

垃圾回收程序运行的时候,会标记内存中存储的所有变量,然后,它会将所有在上下文中的变量,以及被上下文引用的变量的标记去掉,在此之后,再被标记的变量就是待删除的了,原因是任何再上下文中的变量都访问不到它们了。

1
2
3
4
5
6
7
8
9
10
let m = 0,
n = 19; //把m,n ,add()标记进入环境
add(m, n); //把a,b,c标记为进入环境
console.log(n); //把a,b,c标记为离开环境,等待垃圾回收

function add(a, b) {
a++;
let c = a + b;
return c;
}

引用计数

语言引擎有-张”引用表”,保存了内存里面所有的资源(通常是各种值)的引用次数。如果一个值的引用次数是0,就表示这个值不再用到了,因此可以将这块内存释放。如果一个值不再需要了,引用数却不为0,垃圾回收机制无法释放这块内存,从而导致内存泄漏。

1
2
const arr = [1,2,3,4];
console.log( 'hello world') ;

上面代码中,数组[1, 2, 3,4]是一个值,会占用内存。变量arr是仅有的对这个值的引用,因此引用次数为1。尽管后面的代码没有用到arr,它还是会持续占用内存,如果需要这块内存被垃圾回收机制释放,只要设置如下:

1
arr = nu1l

常见的内存泄漏情况

在函数中意外的全局变量

1
2
3
function foo(arg) {
bar = "this is a global variable"
}

this意外创建全局变量

1
2
3
4
function foo() {
this.variable = "potential accidental global"
}
foo();//foo调用自己,this指向全局对象window.

闭包

1
2
3
4
5
6
7
function bindEvent() {
let obj = document.createElemet('xxx')
let unused = function (){
consloe.log(obj,'闭包引用obj,obj不会被释放')

obj = null;//解决方法
}

没有及时清理DOM元素

1
2
3
4
5
6
const refA = document.getElementById('refA');
document.body.removeChild(refA); //删除了DOM
console.log(refA); //但是还存在引用
refA = null; //解除引用
console.log(refA);

在使用事件监听addEventListener的时候,在不见听的情况在使用removeElementListener取消

函数柯里化就是只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数

1
2
3
4
5
6
7
8
9
10
11
12
function add(x, y) {
return x+y
}

function curryingAdd(x) {
return function (y) {
return x + y
}
}

console.log(add(1, 2)) //3
console.log(curryingAdd(1)(2)) //3

函数柯里化的好处

1、参数复用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function check(reg, txt) {
return reg.test(txt)
}

console.log(check(/\d+/g, 'test'))
console.log(check(/[a-z]+/g, 'test'))

//使用柯里化之后
function curryingCheck(reg) {
return function (text) {
return reg.test(text)
}
}

let hasNumber = curryingCheck(/\d+/g)
let hasString = curryingCheck(/[a-z]+/g)

console.log(hasNumber('783927382'))
console.log(hasString('test1'))

如上面的例子,使用柯里化函数之后,先将正则表达式作为第一个参数传进去作为模板,后面可以利用这个模板传入要检测的字符,从而实现多次调用。

2、延迟运行

bind函数的实现,bind绑定this的时候是不会自动执行的,需要手动执行。

1
2
3
4
5
6
7
Function.prototype.bind = function (context) {
var _this = this
var args = Array.prototype.slice.call(arguments, 1) //将类数组变成数组,并返回arguments中除了第一个参数的剩余参数,因为第一个参数是绑定的上下文
return function() {
return _ this.apply(context, args)
}
}

ReferenceError 引用错误

变量或者函数未定义时,会报ReferenceError

1
2
3
4
5
6
7
8
9
10
unction checkAge(age) {
if (age < 18) {
const message = "sorry, you are too young"
} else {
const message = "yay, you are old enough!"
}
return message //ReferenceError: message is not defined
}

console.log(checkAge(21))

TypeError类型错误

1、 调用对象不存在的方法

2、

SyntaxError(语法错误)

高级泛型

1.Partial 此工具的作用是将泛型中的全部属性变成可选的

定义:
1
2
3
4
type Partial<T> = {
[P in keyof T]?: T[p]
}
//T是传入的泛型 P是泛型T中的属性名,T[P]则可获取到泛型T中属性名为P的属性值
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
例子:
interface IUser {
name: string
age: number
department: string
}
type optional = Partial<IUser>

// optional的结果如下
type optional = {
name?: string | undefined;
age?: number | undefined;
department?: string | undefined;
}

2. Pick<T,K>此工具的作用是将泛型T中的属性名为K的属性提取出来

定义:
1
2
3
4
type Pick<T, K extends keyof T> = {
[P in K]: T[P]
}
//K是泛型T的键名集合,P和K都是键名,P是K的一个子集
举例:
1
2
3
4
5
6
7
8
9
10
11
12
interface IUser {
name: string
age: number
department: string
}
type option = Pick<TUser, 'name'|'age'>

// optional的结果如下
type optional = {
name: string
age: number
}

3.Readonly< T >,此工具的作用是将T泛型中的所有属性都变成只读类型(即不可以对属性重新赋值)

定义:
1
2
3
type Readonly<T> = {
readonly [P in keyof T]: T[P]
}
举例:
1
2
3
4
5
6
7
8
9
10
11
12
13
interface IUser {
name: string
age: number
department: string
}
type option = Readonly<TUser>

// optional的结果如下
type optional = {
readonly name: string
readonly age: number
readonly department: string
}

4. Exclude <T, U>此工具的作用是在T类型中有U类型就去掉

定义:
1
2
3
4
5
type Exclude<T, U> = T extends U? never: T

我们这里用 MyTypes 也就是 ‘name’ | ‘age’ | ‘height’ 去代表 T
用 name 属性去代表第二个泛型 U
T extends U 就判断是否’name’ | ‘age’ | ‘height’ 有 name, 有name就返回never,就代表将其排除
举例:
1
2
3
4
5
MyTypes = ‘name’ | ‘age’ | ‘height’
type optionl = Exclude<MyTypes,'height'>

// optional的结果如下
type optional = ‘name’ | ‘age’
###增加小知识###

extends 可以用作继承,也可以用作条件类型,或者类型约束,具体取决于使用场景。

interfaceclass 上时,表示继承

1
2
3
class Dog extends Animal{}
// interface
interface Dog extends Animal{}

在泛型中,可以用作类型约束,如下:

1
2
3
4
/ 这里约束 arg 必须有 length 属性
function log<T extends {length: number}>(arg: T):void{
console.log(arg.length)
}

5.ReturnType< T> 获取泛型T的返回值类型

定义:
1
2
3
4
5
6
type ReturnType<T extends (...arg: any) => any> = T extends (
...arg: any
) => infer U
? U
: never;
//infer表示U是一个不确定的值
举例:
1
2
3
4
5
6
7
8
const fn = (v: boolean) => {
if (v) {
return 1;
} else {
return 2;
}
};
type a = MyReturnType<typeof fn>; //type a = 1 | 2

6. Parameters获取函数参数的类型

定义:
1
2
3
4
5
type Parameters<T extends (...args: any[]) => any> = T extends (
...args: infer P
) => any
? P
: never;
举例:
1
2
3
4
5
function getUser(name: "string") {
return { name: "xxx", age: 10 };
}
type a = Parameters<typeof getUser>;
//type a = [name: "string"]
7.Omit<T, K> 省略泛型T中属性名为K的属性
定义:
1
2
3
type Omit<T, K extends string | number | symbol> = {
[P in Exclude<keyof T, K>]: T[P];
};
举例:
1
2
3
4
5
6
7
8
9
10
interface Todo {
title: string;
description: string;
completed: boolean;
}

type TodoPreview = Omit<Todo, "description" | "title">;
//type TodoPreview = {
completed: boolean;
}

发现自己很久都没有写总结了,罪过罪过。最近烦心事有点多,为了转移点注意力来更下我的小天地。

这里我就不写gitHub账号注册的步骤了,默认你自己已经有一个帐号了。

创建仓库

首先在你的gitHub新建一个仓库,仓库按照一般的仓库来起名就好,不用像hexo那样在后面加.io,并且把SSH (注意要选ssh,选https会出错)地址复制下来后面要用到

将本地代码上传到github上

1
2
3
4
5
6
7
在项目所在的文件夹执行以下步骤命令
1、git init
2、git add .
3、git commit -m "你要写的提交信息"
4、git remote add origin git@github.com:zhisui/qingtian.git
5、git pull origin master
6、git push -u origin master 执行这句可能会出错,git push -f origin master

修改本地React项目的package.json文件

配置homepage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"name": "my-qingtian",
"version": "0.1.0",
"homepage": "https://zhisui.github.io/qingtian", //就是这个地方了,这个创库地址可以按照你自己的想法适当修改一下
"private": true,
"dependencies": {
"@fontsource/roboto": "^4.5.0",
"@material-ui/core": "^4.12.3",
"@material-ui/icons": "^4.11.2",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"axios": "^0.21.4",
"bricks.js": "^1.8.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "4.0.3",
"web-vitals": "^1.0.1"
},

配置发布选项

在scripts字段条件下面这两句话

1
2
3
4
5
6
7
8
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"predeploy": "npm run build", //加上它
"deploy": "gh-pages -d build" //加上它
},

安装gh-pages

1
npm install gh-pages --save-dev

将项目代码编译之后部署到github上

1
2
npm run build
npm run deploy
1
npm run deploy

配置完之后,我们回到github上的仓库,会发现原先的项目多了一个gh-pages分支,里面存放的是我们打包编译完成之后的静态文件。

切换到setting下,将Source里面的路径选为gh-pages ,对应会有一个链接,这个链接就是刚才在pakeage.json文件配置的那个,打开它就打开了新世界的大门。完结,撒花!!!!!!~~~~~~~~

这个小demo分为以下五个文件

index.js 主文件 context的提供者

Page.js 作为中间组件

Paragraph.js和Title.js是context的消费者

context.js 声明定义context,并初始化consumer和provider

1
2
3
4
5
6
7
////////context.js//////////

import React from "react"
const ThemeContext = React.createContext()

export const ThemeProvider = ThemeContext.Provider;
export const ThemeConsumer = ThemeContext.Consumer;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
///////////Page.js/////////////

import React from 'react'
import Paragrapha from "./Paragraph"
import Title from "./Title"

class Page extends React.Component{
render(){
return(
<>
<Title/>
<Paragrapha/>
</>
)
}
}
export default Page //默认导出不需要加{},不是默认导出要记得加,导入演示如此
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/////////Title.js////////////
import React from 'react';

import {ThemeConsumer} from "./context"
class Title extends React.Component{
render(){
return(
<ThemeConsumer>
{theme=><h1 style={{color:theme}}>title</h1>} //这里的theme就是传入的context
</ThemeConsumer>
)
}
}

export default Title
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
////Page。js///////

import React from 'react'
import Paragrapha from "./Paragraph"
import Title from "./Title"

class Page extends React.Component{
render(){
return(
<>
<Title/>
<Paragrapha/>
</>
)
}
}

export default Page

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
////////index.js//////////

import { render } from '@testing-library/react';
import React from 'react';
import ReactDOM from 'react-dom';
import {ThemeProvider} from "./context"
import Page from "./Page"

class App extends React.Component{
constructor(props){
super(props)
this.state={
color:"red"
}
}

handleChange=(e)=>{
this.setState({
color: e.target.value
})
}

render(){
return(
<>
<select onChange={(e)=>this.handleChange(e)}>
<option value="red"> 红色</option>
<option value="blue">蓝色</option>
</select>
<ThemeProvider value={this.state.color}>
<Page/>
</ThemeProvider>
</>
)
}
}


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

最终实现效果

选择蓝色选项卡,可以将下面两行文字变成蓝色。

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import { render } from '@testing-library/react';
import React ,{useEffect, useState} from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import img from './logo512.png'
// import MyErrorBoundery from './MyErrorBoundery';
// import{ThemeContext, themes} from './theme-context';
// import ThemedButton from'./themed-button';
// import PropoTypes from "prop-types"

const useForm = (init)=>{
const [form, setForm] = useState(init)
const handleChange = (e)=>{
setForm({...form, [e.target.name]: e.target.value})
}
return [form, handleChange]
}

function Tool(){
useEffect(()=>{

const focusUseName=(e)=>{

if(e.keyCode===9){
document.querySelector('input[name="usename"]').style.borderColor="red"
}
}
window.addEventListener('keydown' ,focusUseName);

return ()=>{
window.removeEventListener('keydown' ,focusUseName)
}
})
return(
<h1>工具人</h1>
)
}


function App(){
const [showTool, setShowTool] = useState(false)
const [form, setForm] =useState({usename:"", password :""});
const [pokemon,setPokemon] =useState({})
const [isLoading, setIsLoading]=useState({}) //解决图片显示不出来的问题
const [number, setNumber]=useState(1)

useEffect( ()=>{
const fn = async ()=>{
setIsLoading(true); //在开始时设置为true
const apiData = await fetch(`https://pokeapi.co/api/v2/pokemon/${number}/`)
const data =await apiData.json()
setPokemon(data)
setIsLoading(false); //在结束时设置为false
console.log(data);
}
fn();
},[number])


// useEffect(()=>{
// console.log('show');
// return ()=>{
// console.log('display');
// }
// },[form.usename])

return(
<>
<input type="text" value={form.usename} name="usename"
onChange={(e=>setForm({...form, usename: e.target.value,}))}
/>

<input type="text" value={form.password} name="password"
onChange={(e=>setForm({ ...form, password: e.target.value}))}
/>
<div>
<button onClick={()=>setShowTool(!showTool)}>召唤工具人</button>
</div>

{ showTool && < Tool />}
<h1>{pokemon.name}</h1>
<button onClick={()=>setNumber(number+1)}>点我召唤神兽</button>
<img alt="" src={pokemon.sprites?.front_default}
width="100px" height="100px" transform="scale(1)"/>
{/* ?是为了解决图片资源访问不到 */}
</>
)

}



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


最近学习react 高阶组件整得我有点难受,一度怀疑自己的智商,虽然知道高阶组件和高阶函数其实是一类东西,但是就是带入不了,所以还是静下心来,好好撸一下高阶函数,凡事还是不能操之过急,太着急的结果往往不如人意。

开始reduce之前,附上今天看到的一段代码

1
2
3
4
5
6
7
8
9
10
11
let arr = [1,2,3];
arr.reduce((pre,cur)=>{
pre.then(()=>{
return new Promise(resolve=>{
setTimeout(() => {
resolve(console.log(cur))
}, 1000);
})
})
return pre;
},Promise.resolve())

猜猜这段代码实现了啥,刚看到的时候给我给整蒙了,不好意思,奈何道行太浅,我看不懂~~~~

所以决定突破高阶组件就从这个reduce函数开始吧,以前真不怎么去用reduce,一直理解成累加器,今天才知道大错特错,准确点来说,应该是一个累计器,自我感觉这词也不是很准确。

reduce介绍

首先reduce是一个高阶函数,它的参数可以是两个参数,第一个参数是函数,第二个参数是初始值,这个初始值很重要,待会再说。先说下第一个参数函数,这个参数函数呢它自己也带有四个参数(pre,value,index, arr),reduce会对arr中的每一个值调用这个参数函数。现在说下reduce的第二个参数,它是函数参数pre的初始值,如果参数函数有返回值,就可以利用reduce的第二个参数去处理这个返回值,具体怎么处理后面细细道来。

现在先来看一段代码

1
2
3
4
5
let arr = [1,2,3];
arr.reduce((pre,current)=>{
console.log(cur);
},9)
//输出:1,2,3

可以看到这段代码reduce的参数函数里面并没有返回值,所以不管reduce的第二个参数是什么,它只是对cur值每个current值进行了console.log处理。

这个效果是不是和forEach的功能一摸一样,我们现在用它来实现forEach功能

实现forEach

1
2
3
4
5
6
7
8
9
10
11
12
const arr = [1, 2, 3];
function forEach(arr,callback){
arr.reduce((pre, current, index,array)=>{
//直接执行callback,这个回调函数在forEach执行的时候直接作为参数写进去
//这也能解释为什么forEach没有返回值
callback(current, index,array)
},0)
}

forEach(arr, (value, index, array)=>{
console.log(value);
})

实现map

1
2
3
4
5
6
7
8
9
10
11
12
13
const arr = [1, 2, 3];

function map(arr,callback){
return arr.reduce((pre, current, index,array)=>{
let result= callback(current, index,array)
pre.push(result)
return pre;
},[])
}
let newArr =map(arr, (value, index, array)=>{
return value*10;
})
console.log(newArr);//[10,20,30]

这里可以看出map和forEach的区别,map具有返回值,而foreEach没有返回值,所以forEach更适合对数据的一次性处理,而map则适合于需要对返回值做二次处理的场景。(其实我觉得如果抛开返回值只是对数组数据处理一下,这两个没啥区别,直接用,纠结个啥)

实现filter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const arr = [1, 2, 3];

function filter(arr,callback){
//这里不要忘记下面要将整个结果返回回去,因为filter是一个函数,要有返回值,
//下面返回的pre是reduce函数处理的结果
return arr.reduce((pre, current, index,array)=>{
//这里result就是每次callback执行后的返回值,为true或false
let result= callback(current, index,array)
if(result){
pre.push(current)
}
return pre;
},[])

}
let newArr =filter(arr, (value, index, array)=>{
return value>1;
})
console.log(newArr);

上面的代码中我们给pre的初始值是一个空数组,经过判断回调函数的返回值,将arr中大于1的值放到pre中。

实现some

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const arr = [1, 2, 3];
function some(arr,callback){
return arr.reduce((pre, current, index,array)=>{
let result= callback(current, index,array)
if(result){
pre=result;
}
return pre;
},false)

}
let newArr =some(arr, (value, index, array)=>{
return value>2;
})
console.log(newArr);

some函数是数组里面有一个值符合回调函数的条件就返回true,将pre的初始值设置为false可以保证当数组里面所有的值都不符合条件的时候返回false.

实现every

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const arr = [1, 2, 3];
function every(arr,callback){
let flag=true;
return arr.reduce((pre, current, index,array)=>{
let result= callback(current, index,array)
if(!result){
flag=false;
}
return flag;
},)

}
let newArr =every(arr, (value, index, array)=>{
return value>0;
})
console.log(newArr);

every的实现其实不用pre的值,但需要一个flag来标记。遍历数组里面的值,如果有一个不符合直接返回false。

reduce常见的应用场景

求和

1
2
3
4
5
6
7
8
const subjectSummary = [
{id: 1, subject: '语文', score: 86},
{id: 2, subject: '数学', score: 93},
{id: 2, subject: '英语', score: 95},
];
const scoreSum = subjectSummary.reduce((pre, current) => {
return pre + current.score;
}, 0);

数组去重

1
2
3
4
5
6
7
const arr = [1, 2, 3, 2, 3, 1, 8, 5];
const newArr = arr.reduce((pre, current) => {
if (!pre.includes(current)) {
pre.push(current);
}
return pre;
}, []);

统计字符串出现次数

1
2
3
4
5
6
7
8
9
const str = 'aabdddeffghhhhyyy';
const countMapper = str1.split('').reduce((pre, current) => {
if (pre.hasOwnProperty(current)) {
pre[current] += 1;
} else {
pre[current] = 1;
}
return pre;
}, {});

分组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const menuGroup = [
{id: 1, name: '用户中心',type: 1},
{id: 2, name: '设置', type: 2},
{id: 3, name: '消息中心', type: 2},
{id: 4, name: '我的', type: 1},
];

const menuMap = menuGroup.reduce((pre, current) => {
if (current.type === 1) {
pre.menuList.push(current);
}
if (current.type === 2) {
pre.subMenuList.push(current);
}
return pre;
}, {menuList: [], subMenuList: []});

综合这几个例子看下来,我感觉pre更像是一个处理器容器。pre的初始值和pre调用的函数决定了pre将如何处理参数函数中的值。

最后,我们来分析一下刚开始那段让人头疼代码

1
2
3
4
5
6
7
8
9
10
11
let arr = [1,2,3];
arr.reduce((pre,cur)=>{
pre.then(()=>{
return new Promise(resolve=>{
setTimeout(() => {
resolve(console.log(cur))
}, 1000);
})
})
return pre;
},Promise.resolve())

这段代码实现的是将1、2、3每隔一秒输出。

pre的初始值是一个Promise.resolve(),同过调用then来处理pre的resolve状态,而这个then(参数也是一个函数)它将cur的值打印出来并且返回一个promise,且状态为resolve(),那么在下一次执行参数函数的时候,这个新返回的promise又会再一次调用then………. ,有点一直调用then的意味,then就是对返回的新Promise做处理。

一、ref引用在DOM元素上

函数组件中的ref

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function  Child (props){
let myRef =React.createRef();

const handleFocus=()=>{
myRef.current.style.borderColor="red";
}
return(
<div>
<input type="text" ref={myRef} ></input>
<input type="button" value="点我输入边框变成红色" onClick={handleFocus}/>
</div>
)
}

ReactDOM.render(
<Child/>,
document.getElementById('root')
);

类组件中的ref

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
class Child  extends React.Component{
constructor(props){
super(props)
this.myRef =React.createRef();
}

handleFocus=()=>{
this.myRef.current.style.borderColor="red";
}

render(){
return(
<div>
<input type="text" ref={this.myRef} ></input>
<input type="button" value="点我输入边框变成红色" onClick={this.handleFocus}/>
</div>
)
}

}

ReactDOM.render(
<Child/>,
document.getElementById('root')
);

将ref放到函数组件和类组件的普通DOM元素上,则它们都可以获取到当前DOM元素的的原生DOM节点,就可以对这个节点进行一系列的操作。

二、ref引用在组件上

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
class Child  extends React.Component{
constructor(props){
super(props)
this.myRef =React.createRef();
}

handleFocus=()=>{
this.myRef.current.style.borderColor="red";
}

render(){
return(
<div>
<input type="text" ref={this.myRef} ></input>
<input type="button" value="点我输入边框变成红色" onClick={this.handleFocus}/>
</div>
)
}
}

class App extends React.Component{
constructor(props){
super(props)
this.myRef =React.createRef();
}
componentDidMount(){
this.myRef.current.handleFocus(); //当组件挂载时直接将输入框变成红色
}
render(){
return(
<Child ref={this.myRef}/> //将ref引用在Child子组件上
)
}
}

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

​ 上面的例子在componentDidMount()方法中直接调用子组件的handleFocus(),从而实现页面一开始显示的时候输入框边框显示红色,说明在组件中引用ref,则ref指向改组件的实例,可以调用实例的方法,因为函数组件没有实例,所以函数组件不能引用ref.

另一种设置refs的方式是采用回调函数的方式, 回调函数的参数是React 组件实例或 HTML DOM 元素 ,ref指向哪一个DOM元素或者组件由这个回调函数决定。

!!!!!!!!!注意!!!!!!!

使用回调函数的方式设置refs 时,在访问DOM节点的时候不需要用current属性

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
class Child  extends React.Component{
constructor(props){
super(props)
this.myRef = null

this.setMyRef = (input)=>{
this.myRef = input;
}

}

handleFocus=()=>{
console.log(this.myRef);
this.myRef.style.borderColor="red";
}

render(){
return(
<div>
<input type="text" ref={this.setMyRef} ></input>
<input type="button" value="点我输入边框变成红色" onClick={this.handleFocus}/>
</div>
)
}
}

当不涉及父组件传递更改的state给子组件时,是可以将props的值复制给state,但是如果需要重新定义一个父组件的state,这时候就会问题。

一般情况下,子组件接收到父组件传来的props,当做变量直接用就可以,但是个别情况下子组件需要将props赋值给state,但是这里会出现一个问题:如果父组件的state更新后,通过props将父组件的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
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
58

class Child extends React.Component {
constructor(props) {
super(props)
this.state = {
list: props.list
}
}
//后期代码添加部分
//componentWillReceiveProps(props) {
//this.setState({
//list: props.list
//})
//}

handleCilck = () => {
this.setState({
list: this.state.list.concat({ name: '小七' })
})
}

render() {
console.log('我是子组件');
return (
<div>
<button onClick={this.handleCilck}>点击在下方列表中添加小七</button>
{this.state.list.map((item, index) => {
return <h1 key={index}>Hello, {item.name}</h1>
})}
</div>
)
}
}

class Parent extends React.Component {
constructor(props) {
super(props)
this.state = {
list: [{ name: '小五' }, { name: '小六' }]
}
}
handleClick = () => {
this.setState({list: [{name: '小一,我是父组件更新的props'}]})
}
render() {
return (
<div>
<button onClick={this.handleClick}>点击父组件更新props</button>;
<Child list={this.state.list} />;
</div>
)
}
}

ReactDOM.render(
<Parent/>,
document.getElementById('root')
);

如图,子组件的props中能接收到跟新后的List,但是子组件的state并不会更新。

要解决这个问题,需要先了解组件生命周期运行机制。

1
2
3
4
5
6
constructor(props) {
super(props)
this.state = {
list: props.list
}
}

上面这段代码是在子组件挂载(初始化)阶段执行的,当父组件的state发生变化时,子组件的props属性就会发生改变(因为子组件是通过prop获取父组件的state.list),父组件更新,子组件也会更新,子组件更新的时候并不会去执行constructor里面的代码,而是执行conmponentWillReceiveProps()方法,所以新的子组件的的props并不会更新。

解决的方法很简单,就是把状态更新重新在conmponentWillReceiveProps()方法刷新一遍。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
componentWillReceiveProps(props) {
this.setState({
list: props.list
})
}

componentWillReceiveProps在初始化render的时候不会执行,它会在Component接受到新的状态(Props)时被触发,一般用于父组件状态更新时子组件的重新渲染。
componentWillReceiveProps中想作任何变更最好都将两个状态进行比较,假如状态有异才执行下一步。不然容易造成组件的多次渲染,并且这些渲染都是没有意义的。
代码优化

componentWillReceiveProps(nextProps) {
if(nextProps.list !== this.props.list){
this.setState({
list: nextProps.list
})
}
}

如图,子组件正常更新渲染。