一、学习收获
1、React无状态组件和有状态组件
2、React内部组件之间的通讯
3、React组件生命周期
4、React原理
5、React路由
二、React组件
1、无状态组件和有状态组件
- 函数组件又叫做无状态组件,类组件又叫做有状态组件
- 状态(state)即数据
- 函数组件没有自己的状态,只负责数据展示(静态的)
- 类组件有自己的状态,负责更新UI,让页面能够跟用户进行交互(动态)
比如:计数器中的值从 0开始,点击按钮,对值进行加1操作,计数器中的值就是 状态。
2、组件中的props特点
-
可以给组件传递任意类型的数据
-
props是只读的对象,只能读取属性的值,无法修改对象
-
注意:使用类组件时,如果写了构造函数,应该将props传递给super(),否则,无法在构造函数中获取到props。
class Hello extends React.Component { constructor() { super(); console.log(this.props); // 这里拿不到props,想要拿到,需要在构造函数和super中写入props参数 } // render中的 this.props能够拿到 render() { return ( <div> <span>姓名:{this.props.name}</span> <span>年龄:{this.props.age}</span> </div> ); } } ReactDOM.render( <Hello name="zhangsan" age={19} />, document.getElementById("root") );
3、组件之间通讯的三种方式
3.1、父组件传递数据给子组件
- 父组件中要提供传递的state数据
- 在父组件中,给子组件标签添加属性,值为state中的数据
- 子组件中通过props接收父组件中传递的数据
// 父组件
class Parent extends React.Component {
state = {
ParentName: "张"
};
render() {
return (
<div>
父组件姓名:{this.state.ParentName}
<Children1 name={this.state.ParentName} />
</div>
);
}
}
// 子组件
class Children1 extends React.Component {
render() {
console.log(this.props);
return <div>子组件接收到父组件的数据:{this.props.name}</div>;
}
}
ReactDOM.render(<Parent />, document.getElementById("root"));
3.2、子组件传递数据给父组件
思路:利用回调函数,父组件提供回调函数,子组件调用回调函数,并且把将要传递的数据作为回调函数的参数进行传递。
- 父组件提供一个回调函数(用于接收数据)
- 在父组件中,给子组件标签中添加属性,值为父组件中的回调函数(this.回调函数名)
- 在子组件中声明一个函数,然后在子组件的函数中,通过该props调用父组件的回调函数
- 将子组件的数据作为参数传递给父组件的回调函数
- 在子组件中触发点击事件(或者其它事件),执行子组件中声明的函数
class Parent2 extends React.Component {
state = {
childName: " "
};
// 父组件提供一个回调函数(用于接收数据)
ParentHandle = data => {
console.log(data);
this.setState({
childName: data
});
};
render() {
return (
<div>
子组件传过来的值:{this.state.childName}
<Children2 fn={this.ParentHandle} />
</div>
);
}
}
class Children2 extends React.Component {
state = {
childName: "张三丰"
};
childHandle = () => {
// 子组件通过该props调用回调函数
// 将子组件的数据作为参数传递给回调函数
this.props.fn(this.state.childName);
};
render() {
return (
<div>
{/* 在子组件中触发点击事件(或者其它事件),执行子组件中声明的函数 */}
<button onClick={this.childHandle}>点击我,把名字传递给父组件</button>
</div>
);
}
}
ReactDOM.render(<Parent2 />, document.getElementById("root"));
3.3、兄弟组件之间的通讯
思想:把状态提升到兄弟组件最近的父组件中,让状态变成共享状态。(状态提升)
- 将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态
- 公共父组件要提供共享状态
- 公共父组件要提供操作共享状态的方法
- 兄弟组件中只要通过props接收状态或者调用操作状态的方法
class Parent3 extends React.Component {
// 公共父组件要提供共享状态
state = {
count: 0
};
// 公共父组件要提供操作共享状态的方法
parentHandle = () => {
this.setState({
count: this.state.count + 1
});
};
render() {
return (
<div>
<Child1 childCount={this.state.count} />
<Child2 childFn={this.parentHandle} />
</div>
);
}
}
class Child1 extends React.Component {
render() {
// 兄弟组件中只要通过props接收状态
return <h1>当前计数器的值:{this.props.childCount}</h1>;
}
}
class Child2 extends React.Component {
render() {
return (
// 兄弟组件中只要通过props调用操作状态的方法
<button
onClick={() => {
this.props.childFn();
}}
>
加1
</button>
);
}
}
ReactDOM.render(<Parent3 />, document.getElementById("root"));
4、Context
Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props,(能够跨组件传递数据,解决了组件嵌套很深,传递数据繁琐的问题)
// Context (跨组件传递数据,解决嵌套层级较深的组件传值)
// 使用React.createContext 创建 Provider对象和 Consumer对象
// Provider对象:用来提供数据
// Consumer对象:用来消费数据
const { Provider, Consumer } = React.createContext();
class App extends React.Component {
render() {
return (
<Provider value="color">
<div>
<Node />
</div>
</Provider>
);
}
}
class Node extends React.Component {
render() {
return (
<div>
<SubNode />
</div>
);
}
}
class SubNode extends React.Component {
render() {
return (
<div>
<Child />
</div>
);
}
}
class Child extends React.Component {
render() {
return <Consumer>{data => <div>{data}</div>}</Consumer>;
}
}
ReactDOM.render(<App />, document.getElementById("root"));
5.1 children属性
- children属性:表示组件标签的子节点。当组件标签有子节点时,props就会有该属性
- children属性与普通的props一样,值可以是任意值(文本、React元素、组件、函数)
children作为文本节点
class App extends React.Component {
render() {
return (
<div>
<Node>我是子节点</Node>
</div>
);
}
}
class Node extends React.Component {
render() {
console.log(this.props);
return (
<div>
组件中的文本内容是
{this.props.children}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
children作为组件
// children 作为组件
class App extends React.Component {
render() {
console.log(this.props);
return <div>{this.props.children}</div>;
}
}
class Node extends React.Component {
render() {
return <div>我是Node组件</div>;
}
}
ReactDOM.render(
<App>
<Node />
</App>,
document.getElementById("root")
);
children作为函数
// children作为函数
class App extends React.Component {
render() {
console.log(this.props);
return <div>{this.props.children()}</div>;
}
}
const handle = () => {
console.log("hello");
};
ReactDOM.render(<App>{handle}</App>, document.getElementById("root"));
5.2 props 校验
前提:
- 对于组件来说,props是外来的,无法保证组件使用者传入什么格式的数据
- 如果传入的数据格式不对,可能会导致组件内部报错
- 关键问题:组件的使用者不知道明确的错误原因
解决办法:
props 校验:允许在创建组件的时候,就制定 props 的类型、格式等
作用:捕获使用组件时因 props 导致的错误,给出明确的错误提示信息,增加组件的健壮性。
步骤:
- 安装 prop-types包,
yarn add prop-types
或npm i prop-types
- 导入 prop-types包
import PropTypes from "prop-types";
- 使用组件名.propTypes = {} 来给组件的props添加校验规则
- 校验规则通过PropTypes对象来指定
函数形式
// 函数形式
const App = props => {
const arr = props.colors;
const lis = arr.map((item, index) => <li key={index}>{item}</li>);
return (
<div>
<ul>{lis}</ul>;
</div>
);
};
// 注意:这里的 propTypes 中的开头的p是小写的。
App.propTypes = {
// 意:这里的PropTypes 的 P 是大写的,是引入上面的。
colors: PropTypes.array
};
ReactDOM.render(
<App colors={["red", "pink"]} />,
document.getElementById("root")
);
组件形式
// Props 校验
// 1、安装和导入 prop-types包
// 2、使用组件名.propTypes = {} 来给组件的props添加校验规则
// 3、校验规则通过PropTypes对象来指定
import PropTypes from "prop-types";
class App extends React.Component {
// state = {
// arr: this.props.colors
// };
render() {
const arr = this.props.colors;
return (
<div>
<ul>
{/* {this.state.arr.map((item, index) => (
<li key={index}>{item}</li>
))} */}
{arr.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}
}
// 注意:这里的PropTypes,跟函数形式不太一样。
App.defaultProps = {
colors: PropTypes.array
};
ReactDOM.render(
<App colors={["red", "pink"]} />,
document.getElementById("root")
);
5.3 props的默认值
使用场景:比如:分页功能,用来显示条数
// props的默认值
const App = (props) => {
return (
<div>
<h1>props的默认值是:{props.PageSize}</h1>
</div>
);
};
App.defaultProps = {
PageSize: 30,
};
ReactDOM.render(<App />, document.getElementById("root"));
三、React组件的生命周期
组件的生命周期:组件从被创建到挂载到页面中运行,再到组件不用时卸载的整个过程。
生命周期的每个阶段总是伴随着一些方法的调用,这些方法就是生命周期的钩子函数。
钩子函数的作用:为开发人员在不同阶段操作组件提供了时机。
只有类组件才有生命周期,函数组件没有生命周期。
1、挂载阶段的钩子函数
钩子函数 | 触发时机 | 作用 |
---|---|---|
constructor | 创建组件时,最先执行 | 1、初始化state 2、为事件处理程序绑定this |
render | 每次组件渲染都会触发 | 1、渲染UI< 2、注意:在render钩子函数中,不能调用setState() 因为setSate(),能够更新state,也能更新UI,只要调用setSate(),就会调用render更新UI,如果再render中再调用setSate()这样就会出现递归更新,导致报错) |
componentDidMount | 组件挂载(完成DOM渲染)后执行 | 1、发送网络请求 2、DOM操作 |
2、更新阶段的钩子函数
执行时机 |
---|
1、组件接收到新的props会触发render,更新UI |
2、调用setSate()会触发render,更新UI |
3、调用forceUpdate()会强制触发render,更新UI |
以上任意一种变化,组件都会重新渲染
钩子函数 | 触发时机 | 作用 |
---|---|---|
render | 每次组件渲染都会触发 | 1、渲染UI |
componentDidUpdate | 组件更新(完成DOM渲染)后执行 | 1、发送网络请求 2、DOM操作 3、注意:如果要调用setState()必须放在一个if条件中,因为在此钩子中调用 setState()也会递归更新 |
componentDidUpdate(prevProps){
console.log(prevProps)
console.log(this.props)
}
if(prevProps.name !=== this.props.name){
this.setState({})
}
3、卸载阶段的钩子函数
钩子函数 | 触发时机 | 作用 |
---|---|---|
componentWillUnmount | 组件卸载(从页面消失) | 执行清理,比如清理定时器 |
四、React组件复用
React组件复用:把多个组件中部分功能相似或者相同的状态或者逻辑进行复用。(复用:state和操作state的方法)。
复用的方式:
- render props模式
- 高阶组件(HOC)
1、render props模式
// 组件的复用
// render props模式
class Mouse extends React.Component {
state = {
x: 0,
y: 0,
};
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY,
});
};
// 监听鼠标的移动事件
componentDidMount() {
window.addEventListener("mouseover", this.handleMouseMove);
}
render() {
// return null;
return this.props.render(this.state);
}
}
class App extends React.Component {
render() {
return (
<div>
<h1>render props 模式</h1>
<Mouse
render={(mouse) => {
return (
<div>
鼠标的位置{mouse.x} {mouse.y}
</div>
);
}}
/>
<Mouse
render={(mouse) => {
return (
<div>
<img
src={logo}
alt=""
style={{
position: "absolute",
left: mouse.x - 96,
top: mouse.y - 96,
}}
/>
</div>
);
}}
/>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
用children替代render(推荐)
import logo from "./assets/logo192.png";
// 组件的生命周期
// class App extends React.Component {
// constructor() {
// super();
// }
// }
// Props 校验
// 1、安装和导入 prop-types包
// 2、使用组件名.propTypes = {} 来给组件的props添加校验规则
// 3、校验规则通过PropTypes对象来指定
import PropTypes from "prop-types";
// 组件的复用
// render props模式
class Mouse extends React.Component {
state = {
x: 0,
y: 0,
};
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY,
});
};
// 监听鼠标的移动事件
componentDidMount() {
window.addEventListener("mouseover", this.handleMouseMove);
}
componentWillUnmount() {
window.removeEventListener("mouseover", this.handleMouseMove);
}
render() {
// return null;
// return this.props.render(this.state);
return this.props.children(this.state);
}
}
Mouse.propTypes = {
children: PropTypes.func.isRequired,
};
class App extends React.Component {
render() {
return (
<div>
<h1>render props 模式</h1>
{/* <Mouse
render={(mouse) => {
return (
<div>
鼠标的位置{mouse.x} {mouse.y}
</div>
);
}}
/> */}
{/* <Mouse
render={(mouse) => {
return (
<div>
<img
src={logo}
alt=""
style={{
position: "absolute",
left: mouse.x - 96,
top: mouse.y - 96,
}}
/>
</div>
);
}}
/> */}
<Mouse>
{(mouse) => {
return (
<div>
鼠标的位置{mouse.x} {mouse.y}
</div>
);
}}
</Mouse>
<Mouse>
{(mouse) => {
return (
<div>
<img
src={logo}
alt=""
style={{
position: "absolute",
left: mouse.x - 96,
top: mouse.y - 96,
}}
/>
</div>
);
}}
</Mouse>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
2、高阶组件(HOC)
目的:实现状态逻辑复用
高阶组件(HOC,Higher-Order Components)是一个函数,接收一个要包装的组件,返回一个增强后的组件
高阶组件内部创建一个类组件,在这个类组件中,提供复用的状态逻辑代码,通过props将复用的状态传递给被包装组件WrappedComponent
const EnhancedComponent = higherOrderComponent(WrappedComponent);
高阶组件的使用步骤
1、创建一个函数,名称以hoc开头
2、指定函数参数,参数应该以大写字母开头(作为要渲染的组件)
3、在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回该类组件。
4、在该组件中,渲染参数组件,同时将状态通过props传递给参数组件
5、调用高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中
// 高阶组件
// 1、创建一个函数,名称以hoc开头
// 2、指定函数参数,参数应该以大写字母开头(作为要渲染的组件)
const hocMouse = (WrappedComponent) => {
// 3、在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回该类组件。
class Mouse extends React.Component {
// 鼠标状态
state = {
x: 0,
y: 0,
};
handleMouseMove = (e) => {
this.setState({
x: e.clientX,
y: e.clientY,
});
};
// 控制鼠标状态的逻辑
componentDidMount() {
window.addEventListener("mousemove", this.handleMouseMove);
}
// 解绑事件
componentWillUnmount() {
window.removeEventListener("mousemove", this.handleMouseMove);
}
render() {
return (
<div>
{/* 4、在该组件中,渲染参数组件,同时将状态通过props传递给参数组件 */}
<WrappedComponent {...this.state}></WrappedComponent>
</div>
);
}
}
// 返回类组件
return Mouse;
};
// 测试高阶组件
const PagePosition = (props) => {
return (
<div>
鼠标当前位置:x:{props.x},y:{props.y}
</div>
);
};
// 图片定位
const PageCatPosition = (props) => (
<img
src={logo}
alt=""
style={{
position: "absolute",
left: props.x - 96,
top: props.y - 96,
}}
/>
);
// 5、调用高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中
const MousePage = hocMouse(PagePosition);
const PageCat = hocMouse(PageCatPosition);
class App extends React.Component {
render() {
return (
<div>
<h1>高阶组件的运用</h1>
{/* 渲染增强后的组件 */}
<MousePage></MousePage>
<PageCat></PageCat>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
高阶组件传递props
在改动代码:
<WrappedComponent {...this.state} {...this.props}></WrappedComponent>
增加
{...this.props}
const hocMouse = (WrappedComponent) => {
// 在内部创建类组件,在类组件中提供复用状态逻辑
class Mouse extends React.Component {
// 鼠标状态
state = {
x: 0,
y: 0,
};
handleMouseMove = (e) => {
this.setState({
x: e.clientX,
y: e.clientY,
});
};
// 控制鼠标状态的逻辑
componentDidMount() {
window.addEventListener("mousemove", this.handleMouseMove);
}
// 解绑事件
componentWillUnmount() {
window.removeEventListener("mousemove", this.handleMouseMove);
}
render() {
console.log(this.props);
return (
<div>
<WrappedComponent {...this.state} {...this.props}></WrappedComponent>
</div>
);
}
}
Mouse.displayName = `hocMouse${getDisplayName(WrappedComponent)}`;
// 返回类组件
return Mouse;
};
const getDisplayName = (WrappedComponent) => {
return WrappedComponent.displayName || WrappedComponent.name || "Component";
};
// 测试高阶组件
const PagePosition = (props) => {
console.log(props);
return (
<div>
鼠标当前位置:x:{props.x},y:{props.y}
</div>
);
};
// 图片定位
const PageCatPosition = (props) => (
<img
src={logo}
alt=""
style={{
position: "absolute",
left: props.x - 96,
top: props.y - 96,
}}
/>
);
const MousePage = hocMouse(PagePosition);
const PageCat = hocMouse(PageCatPosition);
class App extends React.Component {
render() {
return (
<div>
<h1>高阶组件的运用</h1>
{/* 渲染增强后的组件 */}
<MousePage a="1"></MousePage>
<PageCat></PageCat>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
五、React原理
1、React的工作原理
1)、UI = f(data){}
UI 就是界面 ,这个界面就是函数执行的结果,是按照函数式编程理念来的,我们所做的东西就是要实现这个函数,改变UI,是通过改变函数中的参数data才行的,改变data,让data来驱动这个函数,这个函数来影响(产生)UI。所以在React中,无论你实现一个组件也好,或者完成其它功能,都是要实现一个函数,改变函数中的data,让data来驱动这个函数,这个函数来影响(产生)UI,也就是说你通过改变data来改变UI,这才是React响应式的理念。
data 就是 props和state
开发者 只需要维护可变的数据 state (what) ,让 react 框架帮助我们处理 DOM 操作(what)。
React 通过 diffing 算法计算如何更新视图。而 diffing 算法有个 的假设前提,开发人员会提供给长列表的每个子项一个 ID,帮助算法进行对比,如下图所示:
通过key保证节点的唯一性,避免了重复的渲染,可以通过上图来进行说明,React认为,如果key都相同,那么就不需要重新的计算并且渲染了,只要key不相同了(比如 新插入了一个 x 这个节点),就需要单独渲染这个 x 节点就行了。
在同一组组件中key的两个要素
1、key 在兄弟节点中是唯一的。
2、key的值是稳定的,key的值必须是固定的,不要总是改变key的值,在实际开发中,key的值不要设置数组的下标索引值,因为这样是不稳定的值,例如上图所示,之前 B的索引值是 1,但是通过改变之后,索引值就变成了 2。
3、三元表达式不需要使用key来进行标记,因为无论三元表达式的条件成立与否,都会有一个对应的值,在对应的位置进行占位。
2)、完成的渲染流程
初始化的渲染流程分为 3 步。
第一步,开发者使用 JSX 语法写 React,babel
会将 JSX 编译为浏览器能识别的 React JS 语法。这一步,一般配合 webpack
在本地进行。
第二步,执行 ReactDOM.render
函数,渲染出虚拟DOM。
第三步,react 将虚拟DOM,渲染成真实的DOM。
页面更新的流程同样也是 3 步。
第一步,当页面需要更新时,通过声明式的方法,调用 setState
告诉 react什么数据变了。
第二步,react 自动调用组件的 render 方法,渲染出虚拟 DOM。
第三步,react 会通过 diffing
算法,对比当前虚拟 DOM 和需要更新的虚拟 DOM 有什么区别。然后重新渲染区别部分的真实 DOM。
- 什么是模块化:从 代码 的角度,去分析问题,把我们编程时候的业务逻辑,分割到不同的模块中来进行开发,这样能够方便代码的重用;
- 什么是组件化:从 UI 的角度,去分析问题,把一个页面,拆分为一些互不相干的小组件,随着我们项目的开发,我们手里的组件会越来越多,最后,我们如果要实现一个页面,可能直接把现有的组件拿过来进行拼接,就能快速得到一个完整的页面, 这样方便了UI元素的重用;组件是元素的集合体;
3)、什么是虚拟DOM(Virtual DOM)
虚拟DOM(VDOM)是一种编程概念,是指虚拟的视图被保存在内存中,并通过诸如ReactDOM这样的库与“真实”的DOM保持同步。这个过程被称为reconciliation。
这种编程方法使用了React的声明式API:你需要告诉React你想让视图处于什么状态,React则负责确保DOM与该状态相匹配。因此你在构建你的应用时不必自己去完成属性操作、事件处理、DOM更新,React会替你完成这一切。
由于“虚拟DOM”更像一种模式而不是特定的技术,有时候我们也会用它表示其他的意思。在React的世界中,由于 “虚拟DOM” 和React元数都是用于表示视图的对象,因此常常被关联在一起。然而React也使用被称为“fibers”的对象来存放组件树的附加信息。在React中,它们也被认为是“虚拟DOM”实现的一部分。
4)、影子DOM(Shadow DOM)和虚拟DOM(Virtual DOM)是相同的概念吗?
不,它们并不是相同的概念。影子DOM是一种浏览器技术,主要被设计用来为Web组件中的变量和CSS提供封装。虚拟DOM是由JavaScript库在浏览器API之上实现的一种概念。
5)、Diff算法
- tree diff:新旧DOM树,逐层对比的方式,就叫做 tree diff,每当我们从前到后,把所有层的节点对比完后,必然能够找到那些 需要被更新的元素;
- component diff:在对比每一层的时候,组件之间的对比,叫做 component diff;当对比组件的时候,如果两个组件的类型相同,则暂时认为这个组件不需要被更新,如果组件的类型不同,则立即将旧组件移除,新建一个组件,替换到被移除的位置;
- element diff:在组件中,每个元素之间也要进行对比,那么,元素级别的对比,叫做 element diff;
- key:key这个属性,可以把 页面上的 DOM节点 和 虚拟DOM中的对象,做一层关联关系;
fiber是React 16中新的引擎。它的主要目的是使虚拟DOM能够进行增量渲染。
React Fiber 是对 React 核心算法的重新实现。
React Fiber 的目标是提高其对动画,布局和手势等领域的适用性。它的最重要的特性是 incremental rendering(增量渲染):它能够将渲染 work 拆分成多块并将这些任务块分散到多个帧执行。
其他的核心特性还包括当新的更新到来时暂停、中止或恢复 work 的能力;为不同类型的更新设置优先级的能力;以及新的并发原语。
6)、什么是 reconciliation?
-
reconciliation
React 用来比较两棵树的算法,它确定树中的哪些部分需要被更新。
-
update
用来渲染 React 应用的数据的改变。通常是
setState
的结果。最终结果将是一次重新渲染
React 的 API 的核心理念是将更新想象成为对整个应用的重新渲染。这允许开发者声明式地推导,而不用担心应用如何从一个状态高效的过渡到另一个状态(A 到 B,B 到 C,C 到 A 等等)。
实际上,对于每个更改重新渲染整个应用程序只适用于最简单的应用程序。在实际的应用中,对性能会有非常大的开销。
Reconciliation 是被大家广泛知晓的 "virtual DOM" 背后的算法。
- 不同的组件类型被认为将生成本质上不同的树。React 将不尝试去进行差异比较,而是直接完全替换旧的树。
- 对于列表的差异比较使用 key 来优化性能。Keys 应当是稳定、可预测且唯一的。
-
-
Scheduling(调度)
Scheduling
-
scheduling
决定 work 何时应该被执行的过程。
-
work
任何计算结果都必须被执行。Work 通常是更新的结果(e.g.
setState
).在 React 目前的实现中,React 递归地遍历树并且调用 render 方法在 event loop 的一次迭代中更新整棵树。不过在将来,它将延迟一些更新来防止掉帧。
这是 React 设计中一个公共的主题。一些受欢迎的库实现了“推”的方法,计算会在新数据到来的时候被执行。但是 React 坚持“拉”的方法,计算能够被推迟直到需要的时候。
React 不是一个通用的数据处理库。它只是设计来构建用户界面的库。我们认为知晓哪些计算是现在就相关的,哪些不是是在应用程序中有独一无二位置的内容。
如果一些东西在屏幕外,我们可以推迟任何与其相关的逻辑。如果数据到来的速度比帧率块,那么我们可以合并并批量更新。我们可以对来自用户与界面互动的工作(如一个按钮点击的动画)和相对不太重要的背后工作(如远程加载数据渲染新的内容)进行优先级的排序来阻止掉帧。
总结一下核心点如下:
- 在 UI 中,并非所有的更新都需要立即生效。实际上,这样做是浪费的,可能会造成掉帧从而影响用户体验。
- 不同类型的更新有不同的优先级。一个动画更新相比于来自数据的更新通常需要更快地被执行。
- 一个以“推”为基础的方案要求应用程序(你,工程师)来决定如何调度工作。而一个以“拉”为核心的方案允许框架(如:React)更智能,来为你做这些决定。
iber 的主要目标是让 React 能够享受到调度带来的好处。明确地说,我们需要能够做到以下几件事:
- 暂停任务并能够在之后恢复任务。
- 为不同的任务设置不同的优先级。
- 重新使用之前完成的任务。
- 如果不在需要则可以终止一个任务。
为了做到这些事,我们首先需要一个方式来拆分这些任务为一个个任务单元。从某种意义上来说,这些任务单元就是 fiber。一个 fiber 代表了一个 任务单元.
view = fn(data)
因此渲染 React 应用程序就类似于调用一个包含其他函数调用的函数。
-
2、一切皆为组件
2.1 组件
组件之间通过props 得到任何数据,都是只读的,不能重新赋值
jsx语法中的标签,并不是直接渲染到了页面上,而是先转换成了React.createElement
这样的js代码,然后再通过render渲染到了页面中
es6 class 类中 静态方法挂载到了constructor
构造器中,实例方法挂载到了原型对象中
class Person {
constructor(name,age){
this.name = name
this.age = age
}
// say 是实例方法
say(){
console.log('您好')
}
// info 是静态方法
static info = 123
}
class Chinese extends Person {
// 使用extends 关键字实现继承,子类的constructor构造函数中,必须显示调用super()方法,这个super表示父类中constructor的引用
constructor(name,age,color,lang) {
super(name,age)
}
}
var p1 = new Person('zs',12)
console.log(p1)
var c1 = new Person('ls',12,'yellow','汉语')
console.log(c1)
c1.say()
console.log(Chinese.info)
react无状态组件和有状态组件的区别 无状态组件:无状态组件(Stateless Component)是最基础的组件形式,由于没有状态的影响所以就是纯静态展示的作用。一般来说,各种UI库里也是最开始会开发的组件类别。如按钮、标签、输入框等。它的基本组成结构就是属性(props)加上一个渲染函数(render)。由于不涉及到状态的更新,所以这种组件的复用性也最强。
有状态组件:在无状态组件的基础上,如果组件内部包含状态(state)且状态随着事件或者外部的消息而发生改变的时候,这就构成了有状态组件(Stateful Component)。有状态组件通常会带有生命周期(lifecycle),用以在不同的时刻触发状态的更新。这种组件也是通常在写业务逻辑中最经常使用到的,根据不同的业务场景组件的状态数量以及生命周期机制也不尽相同。
2.1 什么时候用 无状态组件?什么时候用有状态组件?
1、如果一个组件,只需要通过外界传递过来的props,渲染固定的页面,就适合使用function
创建出来的无状态组件。
2、如果一个组件需要存放自己的私有数据,同时需要在组件的不同阶段执行不同的业务逻辑,就适合使用有状态组件。
在React中,我们通常通过props和state来处理两种类型的数据。props是只读的,只能由父组件设置。state在组件内定义,在组件的生命周期中可以更改。基本上,无状态组件使用props来存储数据,而有状态组件(也称为智能组件)使用state来存储数据。
总的来说:无状态函数式写法 优于React.createClass,而React.Component优于React.createClass。能用React.Component创建的组件的就尽量不用React.createClass形式创建组件。
在React中创建组件有三种方式:
- ES5写法:
React.createClass
- ES6写法:
React.Component
- 无状态的函数写法,又称为纯组件
3、声明式编程
声明式与命令式的区别
- 命令式编程:命令“机器”如何去做事情(how),这样不管你想要的是什么(what),它都会按照你的命令实现。
- 声明式编程:告诉“机器”你想要的是什么(what),让机器想出如何去做(how)。
jQuery是命令式的编程,jQuery让你去使用它的API,去做一些事情,如果jQuery的API改变了,我们写的的代码也要改变,因为API发生变化了,我们需要修改之前的API,所以这样的设计是很不友好的。
React中的写的code不大可能调用系统级别的API,相反,我们是实现一些函数,去响应,我们写的代码,只是声明我们要做什么,怎么去做,就是React来完成了,我们不用去关心,即使React的API改变了,也不会影响。
4、setState() 说明
-
setState() 是异步更新数据的
-
可以多次调用setState(),但是只会触发一次render(重新渲染)
class App extends React.Component {
state = {
num: 0,
};
handleAdd = () => {
// 异步,多次调用this.setState,里面的num不能累加
this.setState({
num: this.state.num + 1,
});
this.setState({
num: this.state.num + 1,
});
console.log(this.state.num);
};
render() {
return (
<div>
<h2>当前的值为:{this.state.num}</h2>
<button onClick={this.handleAdd}>+1</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
- 推荐语法
class App extends React.Component {
state = {
num: 0,
};
handleAdd = () => {
// 推荐使用该语法:该语法也是异步的,但是多次调用this.setState,里面的num是累加的。
// state:表示最新的state
// props: 表示最新的props
this.setState((state, props) => {
return {
num: state.num + 1,
};
});
this.setState((state, props) => {
return {
num: state.num + 1,
};
});
console.log(this.state.num);
};
render() {
return (
<div>
<h2>当前的值为:{this.state.num}</h2>
<button onClick={this.handleAdd}>+1</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
- setState() 的第二个参数
setState({},callback)
class App extends React.Component {
state = {
num: 0,
};
handleAdd = () => {
// 推荐使用该语法:该语法也是异步的,但是多次调用this.setState,里面的num是累加的。
// state:表示最新的state
// props: 表示最新的props
this.setState(
(state, props) => {
return {
num: state.num + 1,
};
},
() => {
console.log(this.state.num); // 这里的num 跟页面中num值是同步的。
}
);
console.log(this.state.num); // 0
};
// componentDidUpdate钩子函数的执行,提前于 this.setState执行
componentDidUpdate() {
console.log("componentDidUpdate", this.state.num); // 这里的num 跟页面中num值是同步的。
}
render() {
return (
<div>
<h2>当前的值为:{this.state.num}</h2>
<button onClick={this.handleAdd}>+1</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
结论:
- componentDidUpdate钩子函数的执行,提前于 this.setState执行,this.setState的第二个参数的执行效果跟componentDidUpdate钩子函数的执行效果都是一样的。
5、组件更新机制
5.1 setState() 两个作用
- 修改state
- 更新UI(组件)
5.2 组件更新过程
父组件更新时,也会更新当前父组件中所有的子组件,不会影响父组件的父组件更新,也不会影响父组件的兄弟组件更新。
5.3 避免不必要的更新渲染
组件的更新机制:父组件的更新会引起子组件的更新。
上面的问题:当子组件没有任何变化时也会进行更新。
解决办法:使用钩子函数shouldComponentUpdate(nextProps, nextState)
使用shouldComponentUpdate()可以让React知道当前状态或属性的改变是否不影响组件的输出,通过返回值决定该组件是否重新渲染,默认返回ture表示重新渲染,返回false时不会重新渲染,而且该方法并不会在初始化渲染或当使用forceUpdate()时被调用。
使用 shouldComponentUpdate(nextProps, nextState) 中的 nextState,避免不必要的更新渲染
class App extends React.Component {
state = {
num: 0,
};
handleAdd = () => {
this.setState(() => {
return {
num: Math.floor(Math.random() * 3),
};
});
};
shouldComponentUpdate(nextProps, nextState) {
// nextState : 最新状态
// this.state: 当前状态
console.log("nextState", nextState, "this.state", this.state);
/* if (nextState.num == this.state.num) {
return false;
}
return true; */
// 以上简写成
return nextState.num !== this.state.num;
}
render() {
console.log("重新渲染");
return (
<div>
<h2>当前的值为{this.state.num}</h2>
<button onClick={this.handleAdd}>加1</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
触发时机:更新阶段的钩子函数,组件重新渲染前执行(shouldComponentUpdate --> render)
使用 shouldComponentUpdate(nextProps, nextState) 中的 nextProps,避免不必要的更新渲染
class App extends React.Component {
state = {
num: 0,
};
handleAdd = () => {
this.setState(() => {
return {
num: Math.floor(Math.random() * 3),
};
});
};
render() {
return (
<div>
<NumData num={this.state.num}></NumData>
<button onClick={this.handleAdd}>加1</button>
</div>
);
}
}
class NumData extends React.Component {
shouldComponentUpdate(nextProps) {
console.log("nextProps", nextProps, "this.props", this.props);
return nextProps.num !== this.props.num;
}
render() {
console.log("重新渲染");
return <h2>当前的值为{this.props.num}</h2>;
}
}
ReactDOM.render(<App />, document.getElementById("root"));
5.4 纯组件(PureComponent)
PureComponent中自动实现了shouldComponentUpdate钩子函数,更加方便
PureComponent原理:纯组件内部通过分别对比props、state的值,来决定是否重新渲染组件
class App extends React.PureComponent {
state = {
num: 0,
};
handleAdd = () => {
this.setState(() => {
return {
num: Math.floor(Math.random() * 3),
};
});
};
render() {
console.log("重新渲染");
return (
<div>
<h2>当前的值为{this.state.num}</h2>
<button onClick={this.handleAdd}>加1</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
class App extends React.Component {
state = {
num: 0,
};
handleAdd = () => {
this.setState(() => {
return {
num: Math.floor(Math.random() * 3),
};
});
};
render() {
console.log("重新渲染");
return (
<div>
<NumData num={this.state.num}></NumData>
<button onClick={this.handleAdd}>加1</button>
</div>
);
}
}
class NumData extends React.PureComponent {
render() {
return <h2>当前的值为{this.props.num}</h2>;
}
}
ReactDOM.render(<App />, document.getElementById("root"));
纯组件内部对比的是 shallow compare(浅层对比),对于数值类型是可以,但是对于引用类型就会出现问题
注意:props或state属性值为引用类型时,应该创建新数据,不要直接修改原数据
class App extends React.PureComponent {
state = {
obj: {
num: 0,
},
};
handleAdd = () => {
// 创建新的对象
const newObj = { ...this.state.obj, num: Math.floor(Math.random() * 3) };
this.setState(() => {
return {
obj: newObj,
};
});
};
render() {
console.log("重新渲染");
return (
<div>
<h2>当前的值为{this.state.obj.num}</h2>
<button onClick={this.handleAdd}>加1</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
同时注意:也不要使用数组的push/unshift等直接修改当前数组的方法,这些都是在修改了原数组,没有创建新的数组,应该使用concat或者slice等这些返回新数组的方法。
this.setState({
list:[...this.state.list,{newData}]
})
六、React路由
路由是一套映射规则,在React中,是URL路径与组件的对应关系,其实就是配置路径与组件的配对,让用户从一个视图,导航到另外一个视图中
1、基本用法
// 引入React路由
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
const About = () => {
return <div>关于页面</div>;
};
class App extends React.Component {
render() {
return (
// 使用Router组件包裹整个应用,Router组件只需要使用一次即可
<Router>
<div>
<h2>React路由</h2>
{/* 指定路由入口 */}
<Link to="/about">跳转至关于页面</Link>
{/* 指定路由出口 */}
<Route path="/about" component={About}></Route>
</div>
</Router>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
2、编程式导航
class Login extends React.Component {
handleLogin = () => {
// 编程式导航 push跳转到某个页面
this.props.history.push("/home");
};
render() {
return (
<div>
<div>登录页面欢迎您</div>
<br />
<button onClick={this.handleLogin}>登录</button>
</div>
);
}
}
const HomePage = (props) => {
const handleBack = () => {
// 编程式导航 go(-1)返回
props.history.go(-1);
};
return (
<div>
<h2>首页</h2>
<button onClick={handleBack}>退出</button>
</div>
);
};
const App = () => {
return (
<Router>
<div>
<h2>注册页面</h2>
<Link to="/login">去登录</Link>
<br />
<br />
<Route path="/login" component={Login} />
<Route path="/home" component={HomePage} />
</div>
</Router>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
3、默认路由
const About = () => {
return <div>关于页面</div>;
};
class App extends React.Component {
render() {
return (
<Router>
<div>
<h2>React路由</h2>
{/* 默认路由 */}
<Route path="/" component={About}></Route>
</div>
</Router>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
4、模糊匹配和精准匹配
在React中,React路由默认是模糊匹配模式,想要精准匹配如下所示,
给默认路由添加 exact属性
<Route exact path="/" component={About}></Route>