Release v3.4.2 · facebook/react-devtools · GitHub
1.react介绍
react是用于构建用户界面的javaScript 库,是一个将数据渲染为HTML视图的开源 JavaScript库由FaceBook软件工程师Jordan Walke创建,2013年5月宣布开源。
why react:原生js的痛点-原生JS通过DOM Api操作DOM繁琐、效率低;浏览器会进行大量的重绘重排;原生JS没有组件化编码方案,代码复用率低。
react 特点:
- 采用组件化模式、声明式编码,提高开发效率及组件复用率。
- React Native 中可以使用 React语法进行移动端开发
- 使用虚拟DOM (通过虚拟DOM对比有差异的地方)+ 优秀的Diffing算法,尽量减少与真实DOM的交互
以下代码中将会使用的依赖库
<!-- 引入react核心库 注意:react核心库要放在react-dom之前引入-->
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
2.虚拟DOM
- 本质上是一个Object对象,一般对象;
- 虚拟DOM比较 ”轻",真实DOM比较 “重”, 因为虚拟DOM是React内部在用,无需真实DOM这么多的属性;
- 虚拟DOM最终会被React转化成真实DOM,呈现在页面上。
// 渲染页面的容器-必须要有--下文为了精简代码不写这个了
<div id="app"></div>
<script type="text/babel"> //注意:要写babel,下文为了精简代码不写这个了
//1.1使用JS创建虚拟DOM
// const VDOM = React.createElement('h1', {id: 'title'},
React.createElement('span',{}, 'hello, React'))
//1.2使用JSX创建虚拟DOM
const VDOM = (
<h1 id="title">
<span>Hello React</span>
</h1>
);
//2.渲染虚拟DOM到页面--必须要有--下文为了精简代码不写这个了
ReactDOM.render(VDOM, document.getElementById("app"));
console.log("虚拟dom>>>>>>", VDOM);
const TDOM = document.getElementById("app1");
console.log("真实dom>>>>>>", TDOM);
debugger;
</script>
对比上图,虚拟DOM上的属性要比真实DOM上的属性少的多,因此操作起来也比较方便。
react创建虚拟DOM的两种方式:JS和JSX方式
//1使用JS创建虚拟DOM
const VDOM = React.createElement(
"h1",
{ id: "title" },
React.createElement("span", {}, "hello, React")
);
//2使用JSX创建虚拟DOM
const VDOM = (
<h1 id="title">
<span>Hello React</span>
</h1>
);
3.JSX
JSX语法规则:
● 定义虚拟DOM时,不要写引号【const vdom=<h1>hello</h1>】
●标签中混入JS表达式时要用 {} 【const vdom=<h1 id={myId}>{myData}</h1>】
●样式的类名指定不用 class,要用className【const vdom=<h1 className="title">hello</h1>
●内联样式,要用style = {{key: value}} 的形式去写【const vdom=<h1 style={{color:'red',fontSize:'30px'}}>hello</h1>
●只有一个根标签
●标签必须闭合
●标签首字母(1)若小写字母开头,则将该标签转为 html同名元素,若html中无该标签,则报错;2)若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错
const myId = "iDKKa";
const myData = "heLLo ReaCt";
const data = ["react", "vue", "angular"];
const vDom = (
<div>
<h1 id={myId.toLowerCase()} className="title">
<span style={{ color: "red", fontSize: "30px" }}>
{myData.toLowerCase()}
</span>
</h1>
<ul>
{data.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
- js语句:if(){},for(){},swith(){case:xxx} 等控制代码走向的,没有值
-
4.Hello React
<!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>Document</title>
<style>
</style>
</head>
<body>
<!-- 容器 -->
<div id="app"></div>
<div id="app1"></div>
<!-- 引入react核心库 注意:react核心库要放在react-dom之前引入-->
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
<script type="text/babel">
//注意:要写babel
//1.1使用JS创建虚拟DOM
// const VDOM = React.createElement('h1', {id: 'title'}, React.createElement('span',{}, 'hello, React')) //纯js创建虚拟dom
//1.2使用JSX创建虚拟DOM
const VDOM = (
<h1 id="title">
<span>Hello React</span>
</h1>
); //jsx创建虚拟dom
ReactDOM.render(VDOM, document.getElementById("app")); //2.渲染虚拟DOM到页面
console.log("虚拟dom>>>>>>", VDOM);
const TDOM = document.getElementById("app1");
console.log("真实dom>>>>>>", TDOM);
debugger;
</script>
</body>
</html>
- 总结:
- CDN方式引入:react核心库 ;react-dom:用于支持react操作DOM;babel:es6转es5,jsx转js
- 过程分两步:(1)创建虚拟DOM(2)渲染虚拟DOM到页面
- 注意:react核心库要放在react-dom之前引入;<script type="text/babel">;要有一个容器 <div id="app"></div>
5.函数式组件
定义组件可通过函数或类实现,分为函数式组件和类式组件
函数式组件
function MyComponent() {
console.log(this);
return <h1>函数式组件适用于简单组件的定义</h1>;
}
ReactDOM.render(<MyComponent />, document.getElementById("app"));
执行了ReactDOM.render(...之后,发生了什么)
- React解析组件标签,找到了MyComponent组件
- 发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实的DOM,随后呈现在页面上。
注意必须有return
6.类式组件
补充类的基本知识
<!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>Document</title>
</head>
<body>
<script type="text/javascript">
class Person{//创建一个Person类
constructor(name,age){
//构造函数中的this指向谁?---类的实例
this.name=name
this.age=age
}
a=1// 类中可以直接写赋值语句
// 一般方法,speak方法放在哪?---类的原型对象上,供实例使用
sayHi(){
console.log(`Hi,I am ${this.name}`);
}
}
const p1=new Person("maidu",12) //创建一个Person类的实例对象
console.log(p1);
p1.sayHi()
//继承
class Student extends Person{
constructor(name,age,grade){
super(name,age)
this.grade=grade
}
//重写从父类继承的方法
sayHi(){
console.log(`Hi,I am ${this.name},我在${this.grade}`);
}
study(){
console.log("我爱学习");
}
}
const s1=new Student("eason",3,"幼儿园")
console.log(s1);
s1.sayHi()
s1.study()
</script>
</body>
</html>
- 类中的构造器不是必须写的,要对实例进行一些初始化的操作,如添加指定属性时才写
- 如果A类继承了B类,且A类型写了构造器,那么A类构造器中的super是必须要调用的
- 类中所定义的方法,都是放在了类的原型对象上,供实列使用
- 类中可以直接写赋值语句【a=1】
类式组件
// 1、创建类式组件
class MyComponent extends React.Component {
// render放在哪?---MyComponent的原型对象上,供实例使用
// render中的this是谁?---MyComponent的实例对象,<=>MyComponent组件实例对象
render() {
console.log(this);
return <h1>类式组件适用于复杂组件的定义</h1>;
}
}
// 2.渲染组件到页面
ReactDOM.render(<MyComponent />, document.getElementById("app"));
执行了ReactDOM.render(...之后,发生了什么)
- React解析组件标签,找到了MyComponent组件
- 发现组件是使用类定义的,随后new 出来该类的实例,并通过该实例调用到原型上的render方法
- 将render 返回的虚拟DOM转为真实DOM,随后呈现在页面上
7.组件三大属性1_state
- state 的值是对象【state={key:value}】
- 状态数据不能直接修改更新,必须要用内置api--- setState
- 组件中render方法中和constructor中的this都为组件实例对象
- 组件自定义方法中的this 为undefined解决办法:bind改变this执行;箭头函数(一般用第二种,通过赋值语句+箭头函数定义事件解决this指向问题)
//构造器调用---1次
constructor(props) {
super(props); //this必须放在super之后使用
console.log("constructor", this);
this.state = { isChinese: true, name1: "maidu", name2: "麦嘟" };
this.cL = this.changeLanguage.bind(this);//1.bind
}
// 2.赋值语句+箭头函数解决this指向问题
changeLanguage = () => {}
- 注意元素绑定事件正确写法:onClick = { this.demo },this.demo是函数的回调
- 错误写法: onClick = { this.demo() }, this.demo() 是函数的执行
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
<script type="text/babel">
class Language extends React.Component {
state = { isChinese: true, name1: "maidu", name2: "麦嘟" };
render() {
const { isChinese, name1, name2 } = this.state;
return (
<div onClick={this.changeLanguage}>{isChinese ? name2 : name1}</div>
);
}
// 赋值语句+箭头函数解决this指向问题
changeLanguage = () => {
let flag = this.state.isChinese;
this.setState({
isChinese: !flag,
});
};
}
ReactDOM.render(<Language />, document.getElementById("app"));
</script>
</body>
</html>
8.组件三大属性2_props
- 在类组件中 直接 this.props就可以拿到 组件传进来的值对象了
- React 的组件的props传值,可以使用三点运算符 展开 Object,仅限于 组件的props的传值
- props是只读,不可以修改
- 构造器是否接受 props,是否传递给super,取决于:是否希望在构造器中通过this访问 props
- ES6 中三点运算符,可以展开数组,但是不可以展开 Object,因为Object不是一个iterator,不是可迭代的
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
console.log(...arr1);
let arr3 = [...arr1, ...arr2];
console.log(arr3);
function sum(...arg) {
return arg.reduce((prev, cur) => prev + cur);
}
console.log(sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
let obj = { name: "maidu", age: 12, action: { eat: "apple" } };
// console.log(...obj);//报错,对象不是一个iterator,不是可迭代的,...不能展开对象
let obj2 = { ...obj }; //..是浅拷贝,仅第一层值不受影响
obj2.name = "麦嘟";
obj2.action.eat = "orange";
console.log(obj);
console.log(obj2);
let obj3 = { ...obj, name: "ct" };
console.log(obj3);
类式组件使用props
class Person extends React.Component {
render() {
console.log('props>>>>>>>',this.props);
const {name,age,sex}=this.props
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
<li>性别:{sex}</li>
</ul>
);
}
}
ReactDOM.render(<Person name="maidu" age="18" sex="男"/>, document.getElementById("app1"));
ReactDOM.render(<Person name="ct" age={16} sex="男" />, document.getElementById("app2"));
const p = { name: "maidu", age: 12, sex: "女" };
ReactDOM.render(<Person {...p} />, document.getElementById("app3"));
React内置了一些 方法 对 props传入的属性进行 检查,15.5版本之后React.PropTypes 已移入另一个包中了
对Props进行属性和必要性限制
// 对标签属性进行类型和必要性限制
Person.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
sex: PropTypes.string,
speak: PropTypes.func, //此处不写function因为function是个关键字
};
// 给标签属性设置默认值
Person.defaultProps = {
sex: "女",
};
function speak() {
console.log("我说话了");
}
也可以把以上代码放在类里面,通过static关键字定义,相当于类的静态成员
class Person extends React.Component {
// 对标签属性进行类型和必要性限制
static propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
sex: PropTypes.string,
speak: PropTypes.func, //此处不写function因为function是个关键字
};
// 给标签属性设置默认值
static defaultProps = {
sex: "女",
};
}
函数式组件使用props
function Person(props) {
const { name, age, sex } = props;
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
<li>性别:{sex}</li>
</ul>
);
}
// 对标签属性进行类型和必要性限制
Person.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
sex: PropTypes.string,
speak: PropTypes.func, //此处不写function因为function是个关键字
};
// 给标签属性设置默认值
Person.defaultProps = {
sex: "女",
};
function speak() {
console.log("我说话了");
}
ReactDOM.render(
<Person name="maidu" age={12} speak={speak} />,
document.getElementById("app1")
);
ReactDOM.render(
<Person name="ct" age={16} sex="男" />,
document.getElementById("app2")
);
const p = { name: "maidu", age: 12, sex: "女" };
ReactDOM.render(<Person {...p} />, document.getElementById("app3"));
9.组件三大属性3_ref
①字符串形式的 ref,官方不建议使用,以后会弃用
绑定组件【ref="input1"】,获取值【 this.refs.input1.value】
class MyComponent extends React.Component {
showData1 = () => {
console.log("refs>>>>>", this, this.refs.input1);
const { input1 } = this.refs;
alert(input1.value);
};
render() {
return (
<div>
<input ref="input1" type="text" placeholder="请输入" />
<button onClick={this.showData1}>按钮</button>
</div>
);
}
}
②回调形式的ref
绑定组件【ref={(c) => (this.input1 = c)】,获取值【this.input1.value】
回调函数形式的ref中回调函数,【ref={(c) => (this.input1 = c)】这种内联形式会执行2次,但是影响不大;可以通过把回调绑定在class的属性上
JSX中注释代码{/* */},使用{}表示里面要写JS代码了,因此这里面才能用JS代码注释的方式来注释
class MyComponent extends React.Component {
state = { isHot: true };
showData1 = () => {
console.log("refs>>>>>", this);
const { input1 } = this;
alert(input1.value);
};
changeWeather = () => {
const { isHot } = this.state;
this.setState({
isHot: !isHot,
});
};
saveInput1 = (c) => {
this.input1 = c;
console.log("@", c);
};
//回调函数形式的ref中回调函数,内联形式会执行2次;可以通过把回调绑定在class的属性上--具体解释见官网
render() {
return (
<div>
<h1>今天天气很{this.state.isHot ? "炎热" : "凉爽"}</h1>
{/* <input
ref={(c) => {this.input1 = c; console.log("@",c)}} //将ref指向的节点c(currentNode)给this.input1
type="text"
placeholder="请输入"
/>*/}
<input
ref={this.saveInput1} //将ref指向的节点c(currentNode)给this.input1
type="text"
placeholder="请输入"
/>
<br />
<br />
<button onClick={this.showData1}>点我获取输入框数据</button>
<button onClick={this.changeWeather}>点我切换天气</button>
</div>
);
}
}
ReactDOM.render(<MyComponent />, document.getElementById("app"));
③createRef:官方最推荐的写法,createRef是只能存一个值,后面的值会直接覆盖前面定义的值,键值对的形式存储。因此通过这种形式绑定N个组件,需要createRef N次,感觉也挺费事。。。
class MyComponent extends React.Component {
myRef1 = React.createRef();
myRef2 = React.createRef();
showData1 = () => {
const { current } = this.myRef1;
alert(current.value);
};
showData2 = () => {
const { current } = this.myRef2;
alert(current.value);
};
render() {
return (
<div>
<input ref={this.myRef1} type="text" placeholder="请输入" />
<button onClick={this.showData1}>按钮</button>
<input
ref={this.myRef2}
type="text"
placeholder="请输入"
onBlur={this.showData2}
/>
</div>
);
}
}
10. 事件处理
通过onXxx属性指定事件处理函数,如onClick,onBlur等
- React使用的是自定义(合成)事件,而不是使用的原生的DOM事件(onclick,onblur等) --- 为了更好的兼容性。react在自定义事件中处理了一些兼容性问题
- React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)--为了高效。事件委托通过事件冒泡实现,把事件委托给组件最外层的元素。
注意:不要过度使用ref:可以通过event.target得到发生事件的DOM元素对象,事件绑定和事件触发作用同一对象通过获取event来减少refs的使用。
class MyComponent extends React.Component {
showData2 = (e) => {
console.log(e.target);
alert(e.target.value); //事件绑定和事件触发作用同一对象通过获取event来减少refs的使用
};
render() {
return (
<input
type="text"
placeholder="请输入"
onBlur={this.showData2}
/>
);
}
}
11.非受控组件&受控组件
(1)非受控组件:相对于受控组件来说,属性没有在 state状态里面维护的都是非受控组件,属性现造现取。
class Login extends React.Component {
handleLogin = (e) => {
e.preventDefault(); //阻止表单默认的提交事件
const { username, password } = this;
alert(`用户名:${username.value},密码:${password.value}`);
};
render() {
return (
<form onSubmit={this.handleLogin}>
用户名:{" "}
<input ref={(c) => (this.username = c)} type="text" name="username" />
密码:
<input ref={(c) => (this.password = c)} type="text" name="password" />
<button>登录</button>
</form>
);
}
}
(2)受控组件:数据都放在 state状态里面维护,相当于 vue的数据双向绑定
class Login extends React.Component {
state = {
username: "",
password: "",
};
//受控组件:
handleLogin = (e) => {
e.preventDefault(); //阻止表单默认的提交事件
const { username, password } = this.state;
alert(`用户名:${username},密码:${password}`);
};
saveUsername = (e) => {
this.setState({ username: e.target.value });
};
savePassword = (e) => {
this.setState({
password: e.target.value,
});
};
render() {
return (
<form onSubmit={this.handleLogin}>
用户名:{" "}
<input onChange={this.saveUsername} type="text" name="username" />
密码:
<input onChange={this.savePassword} type="text" name="password" />
<button>登录</button>
</form>
);
}
}
12.高阶函数&函数的柯里化
高阶函数: 如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数
- 若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数
- 若A函数,调用的返回值仍然是一个函数,那么A就可以称之为高阶函数
- 常见的高阶函数: Promise、setTimeout、arr.map()、arr.reduce()等
函数的柯里化: 通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
//函数柯里化
saveFormData = (dataType) => {
return (e) => {
this.setState({ [dataType]: e.target.value });
};
};
render() {
return (
<form onSubmit={this.handleLogin}>
用户名:{" "}
<input
onChange={this.saveFormData("username")}
type="text"
name="username"
/>
密码:
<input
onChange={this.saveFormData("password")}
type="text"
name="password"
/>
<button>登录</button>
</form>
);
}
this.setState({[dataType]: event.target.value}) 中的 [dataType]为什么可以读变量呢?---在数组里面是 array[a] =1 就可以赋值数据, 同理
不使用函数柯里化的实现
saveFormData = (dataType, e) => {
this.setState({ [dataType]: e.target.value });
};
<input
onChange={(e) => this.saveFormData("username", e)}
type="text"
name="username"
/>;
13.DOM的 diffing算法
逐层比较,最小粒度是标签
经典面试题
react/vue 中key有什么作用?(key的内部原理是什么?)
简单的说:key是虚拟DOM对象的标识,在更新显示时key起到及其重要的作用
详细说:当状态中的数据发生变化时,react会根据 【新数据】生成 【新的虚拟DOM】,随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
a. 旧虚拟DOM中找到了与新虚拟DOM相同的key
1. 若虚拟DOM中内容没有变,直接使用之前的真实DOM
2. 若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
b. 旧虚拟DOM中未找到与新虚拟DOM相同的key
根据数据创建新的真实的DOM,随后渲染到页面
为什么遍历列表时key最好不要用index?
用index作为key可能会引发的问题:
- 若对数据进行:逆序添加、逆序删除等破环顺序操作:会产生没有必要的真实DOM更新 =》界面效果没有问题,但效率低
- 如果结构中还包含输入类的DOM:会产生错误DOM更新 =》界面有问题
- 注意:如果不存在对数据的逆序添加、逆序删除等破环顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
开发中如何选择key?
- 最好使用每条数据的唯一标识作为key,比如:id、手机号、身份证号、学号等唯一值
- 如果确定只是简单的展示数据,用index也是可以的。