1. react基础
1.1 React是什么
React是一个用于构建用户界面的 JavaScript 库
1.2 React能干什么?
- 可以通过组件化的方式构建 构建快速响应的大型
Web应用程序
1.3 React如何干的?
1.3.1 声明式
-
声明式 使用声明式的编写用户界面,代码可行方便调试
-
声明式渲染和命令式渲染
- 命令式渲染 命令我们的程序去做什么,程序就会跟着你的命令去一步一步执行
- 声明式渲染 我们只需要告诉程序我们想要什么效果,其他的交给程序来做
let root = document.getElementById('root');
//声明式
ReactDOM.render(<h1 onClick={()=>console.log('hello')}>hello</h1>,root);
//命令式
let h1 = document.createElement('h1');
h1.innerHTML = 'hello';
h1.addEventListener('click',()=>console.log('hello'));
root.appendChild(h1);
1.3.2 组件化
- 组件化 把页面拆分为一个个组件,方便视图的拆分和复用,还可以做到高内聚和低耦合
1.3.3 一次学习,随处编写
- 可以使用React开发Web、Android、IOS、VR和命令行程序
- ReactNative 使用 React 来创建 Android 和 iOS 的原生应用
- React 360是一个创建3D和VR用户交互的框架
1.4 React干的怎么样?
1.4.1 优点
- 开发团队和社区强大
- 一次学习,随处编写
- API比较简洁
1.4.2 缺点
- 没有官方系统解决方案,选型成本高
- 过于灵活,不容易写出高质量的应用
1.5 其它扩展
- JSX实现声明式
- 虚拟DOM可以实现跨平台
- React使用的设计模式
- 自己React大型架构经验
2.为什么React会引入JSX?
- 题目分析 方案选型,考察知识广度
- 解题思路
- 解释概念?
- 想实现什么目的?
- 有哪些可选方案,为什么这个方案最好
- JSX的工作原理?
2.1 JSX是什么
- jsx
- JSX是一个
JavaScript的语法扩展,JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式 - JSX其实是
React.createElement的语法糖
2.2 React想实现什么目的?
- 需要实现声明式
- 代码结构需要非常清晰和简洁,可读性强
- 结构、样式和事件等能够实现高内聚低耦合,方便重用和组合
- 不想引入新的的概念和语法,只写JavaScript
2.3 为什么JSX最好
2.3.1 模板
- Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据
- 引入太多概念,比如Angular就引入了控制器、作用域、服务等概念
<button v-on:click="counter += 1">增加 1</button>
2.4 JSX工作原理
2.4.1 安装
npm install @babel/core @babel/plugin-syntax-jsx @babel/plugin-transform-react-jsx @babel/types --save
2.4.2 AST抽象语法树
- 抽象语法树(Abstract Syntax Tree,AST)是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构
2.4.3 babel工作流
2.4.5 新转换
const babel = require("@babel/core");
const sourceCode = `<h1 id="title" key="title">hello</h1>`;
const result = babel.transform(sourceCode, {
plugins: [['@babel/plugin-transform-react-jsx',{runtime:'automatic'}]]
});
console.log(result.code);
/**
import { jsx as _jsx } from "react/jsx-runtime";
_jsx("h1", {
id: "title",
children: "hello"
}, "title");
*/
3 实现虚拟DOM
React.createElement函数所返回的就是一个虚拟DOM- 虚拟DOM就是一个描述真实DOM的纯JS对象
3.2.1 src\index.js
src\index.js
import React from './react';
let virtualDOM = (
<div id="A1" key="A1">
<div id="B1" key="B1">B1</div>
<div id="B2" key="B2">B2</div>
</div>
)
console.log(virtualDOM);
3.2.2 src\react.js
import {createElement} from './ReactElement';
const React = {
createElement,
};
export default React;
3.2.3 src\ReactSymbols.js
const symbolFor = Symbol.for;
export let REACT_ELEMENT_TYPE = symbolFor('react.element');
3.2.4 ReactElement.js
src\ReactElement.js
//https://gitee.com/mirrors/react/blob/v17.0.1/packages/react/src/ReactElement.js#L348
import { REACT_ELEMENT_TYPE } from './ReactSymbols';
const RESERVED_PROPS = {
key: true,
ref: true,
_store: true,
__self: true,
__source: true,
};
export function createElement(type, config, children) {
const props = {};
let key = null;
if (config != null) {
key = config.key;
}
for (let propName in config) {
if (!RESERVED_PROPS.hasOwnProperty(propName)) {
props[propName] = config[propName];
}
}
const childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
const childArray = Array(childrenLength);
for (let i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
props.children = childArray;
}
const element = {
$$typeof: REACT_ELEMENT_TYPE,
type,
key,
props
};
return element;
}
4. 函数组件和类组件的相同点和不同点?
-
组件允许你将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思
-
题目分析 本题属于差异题
-
解题思路
- 相同点
- 不同点
4.1 实现组件
4.1 实现函数组件
4.1.1 src\index.js
import React from './react';
let virtualDOM = (
<div id="A1" key="A1" style={style}>
<div id="B1" key="B1" style={style}>B1</div>
<div id="B2" key="B2" style={style}>B2</div>
</div>
)
+function FunctionComponent(){
+ return virtualDOM;
+}
+let functionVirtualDOM = <FunctionComponent/>;
console.log(functionVirtualDOM);
4.2 实现类组件
4.2.1 src\index.js
import React from './react';
let virtualDOM = (
<div id="A1" key="A1" style={style}>
<div id="B1" key="B1" style={style}>B1</div>
<div id="B2" key="B2" style={style}>B2</div>
</div>
)
+class ClassComponent extends React.Component{
+ render(){
+ return virtualDOM;
+ }
+}
+let functionVirtualDOM = <ClassComponent/>;
console.log(functionVirtualDOM);
4.2.3 src\react.js
src\react.js
import {createElement} from './ReactElement';
+import {Component} from './ReactBaseClasses';
const React = {
createElement,
+ Component
};
export default React;
4.3 相同点和不同点
4.3.1 相同点
- 它们都可以接收属性并且返回React元素
4.3.2 不同点
- 编程思想不同: 类组件需要创建实例,是基于面向对象的方式编程,而函数式组件不需要创建实例,接收输入,返回输出,是基于函数式编程的思路来编写的
- 内存占用:类组件需要创建并保存实例,会占用一定内存,函数组件不需要创建实例,可以节约内存占用
- 捕获特性:函数组件具有值捕获特性
- 可测试性: 函数式组件更方便编写单元测试
- 状态: 类组件有自己的实例,可以定义状态,而且可以修改状态更新组件,函数式组件以前没有状态,现在可以使用useState使用状态
- 生命周期: 类组件有自己完整的生命周期,可以在生命周期内编写逻辑,函数组件以前没有生命周期,现在可以使用useEffect实现类似生命周期的功能
- 逻辑复用: 类组件可以通过继承实现逻辑的复用,但官方推荐组件优于继承,函数组件可以通过自定义Hooks实现逻辑的复用
- 跳过更新: 类组件可以通过
shouldComponentUpdate和PureComponent来跳过更新,而函数式组件可以使用React.memo来跳过更新 - 发展前景: 未来函数式组件将会成为主流,因为它可以更好的屏蔽this问题、规范和复用逻辑、更好的适合时间分片和并发渲染
4.3.2.1 捕获特性
import React from 'react';
import ReactDOM from 'react-dom';
class ClassComponent extends React.Component{
state = {number:0}
handleClick = ()=>{
setTimeout(()=>console.log(this.state.number),3000);//1
this.setState({number:this.state.number+1});
}
render(){
return (
<div>
<p>{this.state.number}</p>
<button onClick={this.handleClick}>+</button>
</div>
);
}
}
function FunctionComponent(){
let [number,setNumber] = React.useState(0);
let handleClick = ()=>{
setTimeout(()=>console.log(number),3000);
setNumber(number+1);
}
return (
<div>
<p>{number}</p>
<button onClick={handleClick}>+</button>
</div>
);
}
let virtualDOM = <FunctionComponent/>;
ReactDOM.render(
virtualDOM,document.getElementById('root')
);
结论: 所以看到这里,你似乎明白了为什么及时我们改变了
num界面的num也改变了,然后打印的结果却没有改变。我们可以确保函数执行的瞬间的值是我们最初捕获到的,而不是我们后面改变的,当一些值的改变比如props的改变导致函数的重新执行也不会影响到我们上一次执行的结果。这就是 Dan 所说的函数式组件捕获了渲染所使用的值,并且我们还能进一步意识到:函数组件真正将数据和渲染紧紧的绑定到一起了。Hooks也给出了获取最新的props和state的方法,就是 useRef
解决上面例子的问题
如果我们确实想在setTimeout中输出最新改变过后的值怎么办呢?正如上面所言:我们可以用useRef。
function FunctionComponent(){
let [number,setNumber] = React.useState(0);
const latestNum = useRef(0);
useEffect(() => {
latestNum.current = number;
});
let handleClick = ()=>{
setTimeout(()=> {
console.log(latestNum.current);
}, 3000);
setNumber(number+1);
latestNum.current = number;
}
return (
<div>
<p>{number}</p>
<button onClick={handleClick}>+</button>
</div>
);
}
4.3.2.2 跳过更新
class PureComponent extends Component {
shouldComponentUpdate(newProps,nextState) {
return !shallowEqual(this.props, newProps)||!shallowEqual(this.state, nextState);
}
}
function shallowEqual(obj1, obj2) {
if (obj1 === obj2) {
return true;
}
if (typeof obj1 != "object" ||obj1 === null ||typeof obj2 != "object" ||obj2 === null) {
return false;
}
let keys1 = Object.keys(obj1);
let keys2 = Object.keys(obj2);
if (keys1.length != keys2.length) {
return false;
}
for (let key of keys1) {
if (!obj2.hasOwnProperty(key) || obj1[key] !== obj2[key]) {
return false;
}
}
return true;
}
6. 请说一下React中有DOM-DIFF算法?
- 在React17+中DOM-DIFF就是根据老的fiber树和最新的JSX对比生成新的fiber树的过程
6.1 React优化原则
- 只对同级节点进行对比,如果DOM节点跨层级移动,则React不会复用
- 不同类型的元素会产出不同的结构 ,会销毁老结构,创建新结构
- 可以通过key标识移动的元素
6.2 单节点
- 如果新的节点只有一个的话
-
type不同
<div> <h1>h1</h1> </div> /*************/ <div> <h2>h2</h2> </div> -
key不同
<div> <h1 key="h1">h1</h1> </div> /*************/ <div> <h2 key="h2">h2</h2> </div> -
type和key都相同
<div> <h1 key="h1">h1</h1> </div> /*************/ <div> <h1 key="h1">h1-new</h1> </div> -
key相同但是type不同,直接删除所有老节点
<div> <h1 key="h1">h1</h1> <h2 key="h2">h2</h2> </div> /*************/ <div> <p key="h1">p</p> </div> -
key不同,删除当前老节点,接着对比下一个节点
<div> <h1 key="h1">h1</h1> <h2 key="h2">h2</h2> </div> /*************/ <div> <h2 key="h2">h2</h2> </div>6.3 多节点
-
如果新的节点有多个节点的话
-
节点有可能更新、删除、新增
-
多节点的时候会经历二轮遍历
-
第一轮遍历主要是处理节点的更新,更新包括属性和类型的更新
-
第二轮遍历主要处理节点的新增、删除和移动
-
移动时的原则是尽量少量的移动,如果必须有一个要动,新地位高的不动,新地位低的动
-
一一对比,都可复用,只需更新
<ul> <li key="A">A</li> <li key="B">B</li> <li key="C">C</li> <li key="D">D</li> </ul> /*************/ <ul> <li key="A">A-new</li> <li key="B">B-new</li> <li key="C">C-new</li> <li key="D">D-new</li> </ul> -
一一对比,key相同,type不同,删除老的,添新的
<ul> <li key="A">A</li> <li key="B">B</li> <li key="C">C</li> <li key="D">D</li> </ul> /*************/ <ul> <div key="A">A-new</div> <li key="B">B-new</li> <li key="C">C-new</li> <li key="D">D-new</li> </ul>
-
key不同退出第一轮循环
<ul> <li key="A">A</li> <li key="B">B</li> <li key="C">C</li> <li key="D">D</li> </ul> /*************/ <ul> <li key="A">A-new</li> <li key="C">C-new</li> <li key="D">D-new</li> <li key="B">B-new</li> </ul>
移动
import * as React from 'react';
import * as ReactDOM from 'react-dom';
let oldStyle = { border: '3px solid red', margin: '5px' };
let newStyle = { border: '3px solid green', margin: '5px' };
let root = document.getElementById('root');
let oldVDOM = (
<ul>
<li key="A" style={oldStyle}>A</li>
<li key="B" style={oldStyle}>B</li>
<li key="C" style={oldStyle}>C</li>
<li key="D" style={oldStyle}>D</li>
<li key="E" style={oldStyle}>E</li>
<li key="F" style={oldStyle}>F</li>
</ul>
)
ReactDOM.render(oldVDOM,root);
setTimeout(()=>{
let newVDOM = (
<ul>
<li key="A" style={newStyle}>A-new</li>
<li key="C" style={newStyle}>C-new</li>
<li key="E" style={newStyle}>E-new</li>
<li key="B" style={newStyle}>B-new</li>
<li key="G" style={newStyle}>G</li>
</ul>
)
ReactDOM.render(newVDOM,root);
},1000);
7. 请说一下你对React合成事件的理解?
7.1 事件工作流
- 事件捕获
- 事件目标
- 事件冒泡
- 事件委托
- 先绑定先执行
7.2 事件差异
| 类型 | 原生事件 | 合成事件 |
|---|---|---|
| 命名方式 | 全小写 | 小驼峰命名 |
| 事件处理函数 | 字符串 | 函数对象 |
| 阻止默认行为 | 返回false | event.preventDefault() |
const handleClick = (event)=>{event.preventDefault();}
// 原生事件
<a href="#" onclick="handleClick()">Button</a>
//合成事件
<a href="#" onClick={handleClick}>Button</a>
7.3 合成事件
-
React把事件委托到document对象上
-
当真实DOM元素触发事件,先处理原生事件,然后会冒泡到 document 对象后,再处理 React 事件
-
React事件绑定的时刻是在reconciliation阶段,会在原生事件的绑定前执行
-
目的和优势
- 进行浏览器兼容,React 采用的是顶层事件代理机制,能够保证冒泡一致性
- 事件对象可能会被频繁创建和回收,因此 React 引入事件池,在事件池中获取或释放事件对象(React17中被废弃)
7.3.1 React17以前
7.3.1.1 使用
import * as React from 'react';
import * as ReactDOM from 'react-dom';
class App extends React.Component {
parentRef=React.createRef();
childRef=React.createRef();
componentDidMount() {
this.parentRef.current.addEventListener("click", () => {
console.log("父元素原生捕获");
},true);
this.parentRef.current.addEventListener("click", () => {
console.log("父元素原生冒泡");
});
this.childRef.current.addEventListener("click", () => {
console.log("子元素原生捕获");
},true);
this.childRef.current.addEventListener("click", () => {
console.log("子元素原生冒泡");
});
document.addEventListener('click',()=>{
console.log("document捕获");
},true);
document.addEventListener('click',()=>{
console.log("document冒泡");
});
}
parentBubble = () => {
console.log("父元素React事件冒泡");
};
childBubble = () => {
console.log("子元素React事件冒泡");
};
parentCapture = () => {
console.log("父元素React事件捕获");
};
childCapture = () => {
console.log("子元素React事件捕获");
};
render() {
return (
<div ref={this.parentRef} onClick={this.parentBubble} onClickCapture={this.parentCapture}>
<p ref={this.childRef} onClick={this.childBubble} onClickCapture={this.childCapture}>
事件执行顺序
</p>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
/**
document捕获
父元素原生捕获
子元素原生捕获
子元素原生冒泡
父元素原生冒泡
父元素React事件捕获
子元素React事件捕获
子元素React事件冒泡
父元素React事件冒泡
document冒泡
*/
7.3.1.2 实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>event</title>
</head>
<body>
<div id="parent">
<p id="child">
事件执行顺序
</p>
</div>
<script>
document.addEventListener('click',dispatchEvent);
function dispatchEvent(event,isCapture){
let paths = [];
let current = event.target;
while(current){
paths.push(current);
current=current.parentNode;
}
for(let i=paths.length-1;i>=0;i--){
let eventHandler = paths[i].onClickCapture;
eventHandler&&eventHandler()
}
for(let i=0;i<paths.length;i++){
let eventHandler = paths[i].onClick;
eventHandler&&eventHandler()
}
}
let parent = document.getElementById('parent');
let child = document.getElementById('child');
parent.addEventListener("click", () => {
console.log("父元素原生捕获");
},true);
parent.addEventListener("click", () => {
console.log("父元素原生冒泡");
});
child.addEventListener("click", () => {
console.log("子元素原生捕获");
},true);
child.addEventListener("click", () => {
console.log("子元素原生冒泡");
});
document.addEventListener('click',()=>{
console.log("document捕获");
},true);
document.addEventListener('click',()=>{
console.log("document冒泡");
});
parent.onClick=() => {
console.log("父元素React事件冒泡");
}
parent.onClickCapture=() => {
console.log("父元素React事件捕获");
}
child.onClick=() => {
console.log("子元素React事件冒泡");
}
child.onClickCapture=() => {
console.log("子元素React事件捕获");
}
/*
父元素React事件捕获
子元素React事件捕获
父元素原生捕获
子元素原生捕获
子元素原生冒泡
父元素原生冒泡
子元素React事件冒泡
父元素React事件冒泡
*/
</script>
</body>
</html>
7.3.2 React17以后
7.3.2.1 使用
import * as React from 'react';
import * as ReactDOM from 'react-dom';
class App extends React.Component {
parentRef=React.createRef();
childRef=React.createRef();
componentDidMount() {
this.parentRef.current.addEventListener("click", () => {
console.log("父元素原生捕获");
},true);
this.parentRef.current.addEventListener("click", () => {
console.log("父元素原生冒泡");
});
this.childRef.current.addEventListener("click", () => {
console.log("子元素原生捕获");
},true);
this.childRef.current.addEventListener("click", () => {
console.log("子元素原生冒泡");
});
document.addEventListener('click',()=>{
console.log("document原生捕获");
},true);
document.addEventListener('click',()=>{
console.log("document原生冒泡");
});
}
parentBubble = () => {
console.log("父元素React事件冒泡");
};
childBubble = () => {
console.log("子元素React事件冒泡");
};
parentCapture = () => {
console.log("父元素React事件捕获");
};
childCapture = () => {
console.log("子元素React事件捕获");
};
render() {
return (
<div ref={this.parentRef} onClick={this.parentBubble} onClickCapture={this.parentCapture}>
<p ref={this.childRef} onClick={this.childBubble} onClickCapture={this.childCapture}>
事件执行顺序
</p>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
/**
document原生捕获
父元素React事件捕获
子元素React事件捕获
父元素原生捕获
子元素原生捕获
子元素原生冒泡
父元素原生冒泡
子元素React事件冒泡
父元素React事件冒泡
document原生冒泡
*/
7.3.2.2 实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>event</title>
</head>
<body>
<div id="root">
<div id="parent">
<p id="child">
事件执行顺序
</p>
</div>
</div>
<script>
let root = document.getElementById('root');
let parent = document.getElementById('parent');
let child = document.getElementById('child');
root.addEventListener('click',event=>dispatchEvent(event,true),true);
root.addEventListener('click',event=>dispatchEvent(event,false));
function dispatchEvent(event,isCapture){
let paths = [];
let current = event.target;
while(current){
paths.push(current);
current=current.parentNode;
}
if(isCapture){
for(let i=paths.length-1;i>=0;i--){
let eventHandler = paths[i].onClickCapture;
eventHandler&&eventHandler()
}
}else{
for(let i=0;i<paths.length;i++){
let eventHandler = paths[i].onClick;
eventHandler&&eventHandler()
}
}
}
parent.addEventListener("click", () => {
console.log("父元素原生捕获");
},true);
parent.addEventListener("click", () => {
console.log("父元素原生冒泡");
});
child.addEventListener("click", () => {
console.log("子元素原生捕获");
},true);
child.addEventListener("click", () => {
console.log("子元素原生冒泡");
});
document.addEventListener('click',()=>{
console.log("document原生捕获");
},true);
document.addEventListener('click',()=>{
console.log("document原生冒泡");
});
parent.onClick=() => {
console.log("父元素React事件冒泡");
}
parent.onClickCapture=() => {
console.log("父元素React事件捕获");
}
child.onClick=() => {
console.log("子元素React事件冒泡");
}
child.onClickCapture=() => {
console.log("子元素React事件捕获");
}
</script>
</body>
</html>
7.4 事件系统变更
-
更改事件委托
- 首先第一个修改点就是更改了事件委托绑定节点,在16版本中,React都会把事件绑定到页面的document元素上,这在多个React版本共存的情况下就会虽然某个节点上的函数调用了
event.stopPropagation(),但还是会导致另外一个React版本上绑定的事件没有被阻止触发,所以在17版本中会把事件绑定到render函数的节点上
- 首先第一个修改点就是更改了事件委托绑定节点,在16版本中,React都会把事件绑定到页面的document元素上,这在多个React版本共存的情况下就会虽然某个节点上的函数调用了
-
去除事件池
- 17版本中移除了事件对象池,这是因为 React 在旧浏览器中重用了不同事件的事件对象,以提高性能,并将所有事件字段在它们之前设置为 null。在 React 16 及更早版本中,使用者必须调用
event.persist()才能正确的使用该事件,或者正确读取需要的属性
- 17版本中移除了事件对象池,这是因为 React 在旧浏览器中重用了不同事件的事件对象,以提高性能,并将所有事件字段在它们之前设置为 null。在 React 16 及更早版本中,使用者必须调用
7.5 案例
React16
import * as React from 'react';
import * as ReactDOM from 'react-dom';
class Dialog extends React.Component{
state = {show: false};
componentDidMount() {
document.addEventListener("click", () => {
this.setState({show: false});
});
}
handleButtonClick = (event) => {
//event.stopPropagation();
event.nativeEvent.stopImmediatePropagation();
this.setState({show: true});
};
render() {
return (
<div>
<button onClick={this.handleButtonClick}>显示</button>
{this.state.show && (
<div onClick={(event) => event.nativeEvent.stopImmediatePropagation()}>
Modal
</div>
)}
</div>
);
}
}
ReactDOM.render(<Dialog />, document.getElementById('root'));
React17
import * as React from 'react';
import * as ReactDOM from 'react-dom';
class Dialog extends React.Component{
state = {show: false};
componentDidMount() {
document.addEventListener("click", () => {
this.setState({show: false});
});
}
handleButtonClick = (event) => {
+ event.stopPropagation();
- //event.nativeEvent.stopImmediatePropagation();
this.setState({show: true});
};
render() {
return (
<div>
<button onClick={this.handleButtonClick}>显示</button>
{this.state.show && (
+ <div onClick={(event) => event.stopPropagation()}>
Modal
</div>
)}
</div>
);
}
}
ReactDOM.render(<Dialog />, document.getElementById('root'));