1. 前言
选择任何一门语言学习都是有时间和金钱的成本的,那么React值不值得学习呢?
2. 学习的必要性?
- 使用组件化开发方式,符合现代Web开发的趋势; 企业前后端项目分离,唯有React是首选
- 技术成熟,社区完善,配件齐全,适用于大型Web项目(生态系统健全)
- 由Facebook专门的团队维护,技术支持可靠
- ReactNative - Learn once, write anywhere: Build mobile apps with React
- 使用方式简单,性能非常高,支持服务端渲染
- React使用前端技术非常全面,有利于整体提高技术水平;此外,有利于求职和晋升,有利于参与潜力大的项目
1. 概念介绍
React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设他们自己的 Instagram 的网站。
做出来以后,发现这套东西很好用,在2013年5月开源了,目前已经成为前端的三大主流框架。
2. React是什么?
React是用于构建用户界面的JavaScript 库,围绕React的技术栈主要包括:React, redux, react-redux, react-router, …
官网
React官网
React中文
3. React具备的特点
????????????组件化:将界面, 分解成独立组件
组件包含
- HTML:界面
- CSS:样式
- JS:数据+逻辑
方便复用和维护
????????????声明式开发
-
声明式
① 关注数据
② 关注视图:结合 JSX , 写界面, 声明数据, 更爽
③ 并不关注, 数据如何渲染到视图 -
例如:修改某一个元素的颜色
① 命令式- 获取到DOM对象
- 直接修改DOM对象的样式属性, 为指定的值
② 声明式:直接声明颜色状态
怎么渲染、什么时候渲染,都不用关心
????????????高效
- 虚拟DOM
将真实的DOM树, 映射成一个虚拟的DOM对象
操作时, 操作虚拟DOM对象,不直接操作DOM对象
合并多个修改操作, 最后一起修改DOM
批量更新, 减少更新的次数 - DOM diff算法
对比计算后, 只更新数据变化的DOM对象
最小化界面重绘
图示
4. React核心
虚拟DOM
- 概念:React将DOM抽象为虚拟DOM,虚拟DOM其实就是用一个对象来描述DOM,通过对比前后两个对象的差异,最终只把变化的部分重新渲染,提高渲染的效率
- 为什么用虚拟DOM:当DOM发生更改时需要遍历DOM对象的属性, 而原生DOM可遍历属性多达200多个, 而且大部分属性与渲染无关, 导致更新页面代价太大
- 虚拟DOM的处理方式?
① 用 JS对象结构表示 DOM 树的结构,然后用这个树构建一个真正的 DOM 树,插到文档当中
② 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
③ 把记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了
Diff算法
- 概念:最小化页面重绘
当我们使用React在render() 函数创建了一棵React元素树,在下一个state或者props更新的时候,render() 函数将会创建一棵新的React元素树
React将对比这两棵树的不同之处,计算出如何高效的更新UI(只更新变化的地方),此处所采用的算法就是diff算法 - 后续讲解
5. 补充
代码封装的粒度
普通代码
↓
函数
↓
类
↓
模块
↓
组件
↓
…
模块化与组件化
当应用的js都以模块来编写的, 这个应用就是一个模块化的应用
当应用是以多组件的方式实现功能, 这上应用就是一个组件化的应用
6. 调试工具
配置react-developer-tools开发调试工具插件
- 下载
React Developer Tools 4.6.0
- 打开谷歌浏览器
chrome://extensions/
- 图示
1. 小demo: 你好,撩课学院
第一步: 引入js库
- CDN库
<script src="https://cdn.staticfile.org/react/16.13.1/umd/react.development.js"></script> 包含核心功能 <script src="https://cdn.staticfile.org/react-dom/16.13.1/umd/react-dom.development.js"></script> 包含dom操作 <script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script> 负责ES6语法降级和JSX语法解析
- npm安装
第二步:react基本使用
- 创建虚拟DOM
react高效的原因, 就是基于操作虚拟DOM这个前提
所谓的虚拟DOM, 讲白点就是一个拥有固定格式的JS对象而已
虚拟DOM保存了真实DOM的层次关系和一些基本属性,与真实DOM一一对应 - 渲染虚拟DOM, 到界面上
这个渲染的函数内部会做很多事
合并多次DOM对象的改变, 统一渲染
通过DOM Diff算法, 计算出变化的部分进行渲染
代码实现 - 创建虚拟DOM的两种方式
① React.createElement 函数
函数原型
示例React.createElement( type, [props], [...children] )
② JSXReact.createElement("button", {onClick: ()=>{console.log("点击了按钮")}}, "这是按钮标签包裹的内容")
- 渲染虚拟DOM到指定容器
ReactDOM.render(虚拟DOM对象, document.getElementById(“root”))
2. Demo1
往容器中插入一个span标签, class为: “it-like”, 内容为: “撩课学院”。 两种实现方式: a) 通过典型js方式; b) JSX方式?
总结
- JSX只是高级语法糖, 最终执行时还是会被转成原生js, 通过babel等方式
- 更加语义化, 更加直观, 代码可读性更高
- 性能相对原生方式更加好一些
① 使用远程CDN
② 本地下载
3. Demo2
典型JS插入方式与JSX插入方式的对比
4. Demo2
JSX常见的界面操作方式?
-
多重标签嵌套
-
js中的变量, 表达式要写在{}内
注意:在虚拟DOM中,只能有一个出口,即最外层只能有一层标签,最外层不能多层标签并列。
-
内联样式通过对象方式引入
-
注释也需要用{}括起来
-
数组遍历
5. 总结
- JSX中添加属性时,使用 className 代替 class , 像label中的for属性,使用htmlFor代替;
- JSX创建DOM的时候,所有的节点,必须有唯一的根元素进行包裹 ;
- JSX语法中,标签必须成对出现,如果是单标签,则必须自闭合;
- 在 JSX 中可以直接使用 JS代码,直接在 JSX 中通过 {} 中间写 JS代码即可;
- 在 JSX 中只能使用表达式,不能出现语句;
- 在 JSX 中注释语法:{/* 中间是注释的内容 */}
1. 基本概念
- 组件
① 一个应用/版块/页面中用于实现某个局部的功能(包括html, js, css等)
② 把这些局部功能组装到一起就形成了完整的一个大的功能
③ 主要目的在于: 复用代码, 提高项目运行效率 - 组件化
如果一个应用是用多组件的方式进行综合开发的, 那么这个应用就是一个组件化应用 - 模块
多个组件形成模块, 或者是一个提供特定功能的js文件, 主要特点在于耦合性低, 可移植性高, 执行效率好 - 模块化
如果一个应用都是用模块的形式来构建的,那么这个应用就是模块化应用 - 对照现实生活,本地 和 本地化
2. 组件的概念
- 虚拟DOM对象的集合
① 将一组虚拟DOM对象, 封装在一起
函数 + 类
② 就构成了一个组件 - 组件内部, 可以处理
数据+业务逻辑
3. 组件的创建方式
-
工厂函数
function Test() { return <div>xxx</div> }
使用
① 无参数functionPerson() { return ( <div> <h2>姓名: 小撩宝宝</h2> <img src="img/xiaoliao.png" width="200" alt=""/> </div> ) } ReactDOM.render(<Person/>, document.getElementById('app'));
② 带参数
function Person(props) { return ( <div> <h2>姓名:{props.name}</h2> <h2>年龄:{props.age}</h2> <h2>爱好:{props.hobby}</h2> <img src={props.icon} width="200" alt=""/> </div> ) } let vDom = <Person name="小撩" age={18} hobby={["打篮球", "打羽毛球", "打乒乓球"]} icon="img/xiaoliao.png"/>;
③ 多组件
function Header(props) { return ( <header style={{backgroundColor: 'red'}}>我是头部</header> ) } function Content(props) { return ( <section style={{backgroundColor: 'green'}}> 我是内容部分 </section> ) } function Footer(props) { return ( <section style={{backgroundColor: 'blue'}}> 我是尾部 </section> ) } function Article(props) { return ( <div> <Header/> <Content/> <Footer/> </div> ) } ReactDOM.render(<Article/>, document.getElementById('app'));
-
ES6语法
class Test extends React.Component { render() { return <div>xxx</div> } }
使用
class Person extends React.Component { render(){ let style = { backgroundColor: 'red', width: 400, height: 400 }; return ( <div style={style}> <h2>姓名:{this.props.name}</h2> <h2>年龄:{this.props.age}</h2> <h2>爱好: </h2> <ul> {this.props.hobby.map((item, index)=>(<li key={index}>{item}</li>))} </ul> </div> ) } } let vDom = <Person name="张三" age={18} hobby={["篮球", "乒乓球", "羽毛球"]}/>; ReactDOM.render(vDom, document.getElementById('app'));
-
❌❌❌❌ES5老语法
React.createClass({ render() { } })
4. 组件的注意
- 组件名称: 首字母必须大写! 只有大写才会被识别成组件
- 虚拟DOM 必须有且只有一个根元素
5. 补充
ReactDOM.render()的渲染流程
- 判定渲染的内容是否是组件(包含组件)→判断依据→首字母是否大写
如果是组件 - 如果是组件
① 判断是工厂函数→获取它返回值→虚拟DOM对象
② 判断是组件类→调用其render()获取→虚拟DOM对象 - 将虚拟DOM对象,转换为真实DOM
- 插入到真实的DOM容器中
1. 组件内部, 通过props属性值来获取
2. 意义
当我们在使用某个组件时, 希望能够在外界控制组件内部的数据,此时, 就需要向组件传值。
3. 方式
- 在书写组件标签时, 直接加自定义属性
<组件 属性=“值”></组件> - 在组件内部
① 工厂函数
② 组件类function Test(props) { return <div>{props.属性}</div> }
class Test extends React.Component { render() { let value = this.props.属性 return <div>{value}</div> } }
4. 默认的props设置
-
通过组件类的 defaultProps 属性为 props 设置默认值
-
代码
① 工厂函数的默认props设置:② 组件类的默认props设置:
5. Props验证
-
意义
① Props 验证使用 propTypes,它可以保证我们的应用组件被正确使用
② 当向 props 传入无效数据时,JavaScript 控制台会抛出警告。 -
使用:React.PropTypes 在 React v15.5 版本后已经移到了 prop-types 库
① 导入<script src="https://cdn.bootcss.com/prop-types/15.6.1/prop-types.js"></script>
② 使用
-
补充:验证器选择
1. React.PropTypes.array:React.PropTypes.arrayOf(React.PropTypes.number) 2. React.PropTypes.bool 3. React.PropTypes.func 4. React.PropTypes.number 5. React.PropTypes.object 6. React.PropTypes.string 7. React.PropTypes.oneOf(['News', 'Photos']) 8. React.PropTypes.oneOfType([ React.PropTypes.string, React.PropTypes.number, React.PropTypes.instanceOf(Message) ]) 9. React.PropTypes.shape({ color: React.PropTypes.string, fontSize: React.PropTypes.number }) 10. 不可空 React.PropTypes.func.isRequired React.PropTypes.any.isRequired
1. 什么是state?
① React 把组件看成是一个状态机(State Machines), 通过状态 (State) 去操作状态机。
② 在开发中, 通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致。
③ 在React 中,只需更新组件的 state,然后根据新的 state 重新渲染用户界(不要操作 DOM)。
2. 意义
根据state状态来渲染界面,后期,只要我们修改state, 那么界面就会自动重新渲染:再次执行render
3. 注意
- 不能直接修改state, 需要通过setState方法
① 此方法内部给出的对象, 会增量更新到原state
② 并不会替换原state - 状态数据封装在组件内部, 不要在外界访问
- 多次state数据"同时修改", 会被合并后, 更新一次
4. Demo: 更改界面内容
-
核心代码
-
运行结果
-
点击后效果
5. Demo:props和state共用
- 核心代码
- 运行效果
props用于外部向组件、组件之间数据传递
state用于控制组件内部数据状态
1. props
- props是指组件间传递的一种方式
- 组件内部的this.props属性是只读的不可修改
2. state
- state是组件内部的状态(数据)
- 不能够直接修改,必须要通过setState来改变值的状态
3. 案例演示
八、组件的事件处理1. DOM 元素的事件处理
on事件名称 = “执行函数(event)”
<button onclick="func()">
激活
</button>
2. react组件的事件处理
- React 事件绑定属性的命名采用驼峰式写法,而不是小写
- 如果使用 JSX 的语法你需要传入一个函数作为事件处理函数
- 例如
<button onClick={func}> 点击 </button>
3. react组件的事件阻止
- 不能使用返回 false 的方式阻止默认行为
- 明确的使用 preventDefault
4. 事件方法中的this
- ????????????箭头函数中的this是在定义函数时绑定,且内部无this. 参照上下文确定this
- 普通函数是在执行函数时绑定, 内部有this, 谁执行就是谁
5. 事件方法中传参
可以使用箭头函数, 二次包装
6. 渲染多个组件方法
将多个组件放到同一个Div构成的虚拟DOM中,并直接渲染最终的虚拟DOM
1. 问题描述
- 表单字段内容的值, 使用state进行绑定
- 导致表单字段, 用户无法修改
2. 原因
值是获取的state中的数据, 数据不变, 界面不会被渲染
3. 解决
监听onChange事件
- 获取event.target.value
- 反向的设置到state, 控制数据变更
1. 定义
Refs 提供了一种方式,用于访问在 render 方法中创建的 DOM 节点或 React 元素
2. 使用场景
- 处理焦点、文本选择或媒体控制
- 触发强制动画
- 集成第三方 DOM 库
3. 注意
官方提示, 如果可以通过声明式实现,则尽量避免使用 refs。话外音: React无法控制局面的时候,就需要直接操作Refs了
4. 案例使用
- 让输入框获得焦点?
- 核心代码
1. 概念
在一个组件内部, 可以直接嵌入另外一个组件,可以完全当做是一个html标签使用:本质不是
2. 注意
最终都会被babel进行转化, 转变为对应层级关系的虚拟DOM对象, 然后再渲染为真实DOM对象
3. 案例实操1
Demo: 完成对学生信息的展示/添加/删除?
① 步骤一
- 静态组件拆解, 核心代码
- 核心代码
② 步骤二
- 动态效果实现(添加/删除/显示), 核心代码如下
- 添加和删除部分
① 在父组件中定义增加和删除的方法,并传递给子组件
② 子组件中调用方法
③ 总结
- 多层组件中, 数据该放在何处?
如果只用于一个组件, 则定义在该组件内容; 如果是运用于多个组件, 则定义在它们的父组件中 - 多层组件中, 数据传递问题?
父组件通过props传递数据给子组件, 子组件不能直接修改父组件的数据, 必须是父组件定义修改数据的函数, 传递给子组件然后由子组件调用 - 静态代码
<!DOCTYPE html> <html lang="ch"> <head> <meta charset="UTF-8"> <title>Title</title> <style> #root { margin: 50px auto; width: 600px } fieldset { border: 1px solid purple; margin-bottom: 20px; } fieldset input { width: 200px; height: 30px; margin: 10px 0; } table { width: 600px; border: 2px solid purple; text-align: center; } thead { background-color: purple; color: #fff; } </style> </head> <body> <div id="root"></div> <script src="./../js/react.development.js"></script> <script src="./../js/react-dom.development.js"></script> <script src="./../js/prop-types.js"></script> <script src="./../js/babel.js"></script> <script type="text/babel"> // 父组件 class App extends React.Component { constructor(props) { super(props); // 初始化状态 this.state = { // 列表 studentArr: [ {name: 'James', age: 36, gender: '男', phone: '18888888888'}, {name: 'Durant', age: 32, gender: '女', phone: '18888866666'}, {name: 'Irving', age: 29, gender: '女', phone: '18888877777'}, {name: 'Paul', age: 34, gender: '男', phone: '18888833333'}, ] } } render() { const {studentArr} = this.state; return ( <div> <Add addListToArr={(e) => this.addListToArr(e)} /> <List studentArr={studentArr} delFromArr={(e) => this.delListFromArrWithIndex(e)} /> </div> ); } /** *根据索引删除一条记录 * @param {number}index */ delListFromArrWithIndex(index) { // 1. 删除 let {studentArr} = this.state; studentArr.splice(index, 1); // 2. 更新状态 this.setState({ studentArr }) } /** * 插入一条学生记录 * @param {object}student */ addListToArr(student) { // 1. 插入数据 let {studentArr} = this.state; studentArr.unshift(student); // 2. 更新状态 this.setState({ studentArr }) } } // 子组件——添加 class Add extends React.Component { constructor(props) { super(props); this.stuNameRef = React.createRef(); this.stuAgeRef = React.createRef(); this.stuGenderRef = React.createRef(); this.stuPhoneRef = React.createRef(); } static propTypes = { addListToArr: PropTypes.func.isRequired } render() { return ( <div> <fieldset> <legend>撩课信息录入系统(React版)</legend> <div> <label>姓名: <input ref={this.stuNameRef} type="text" placeholder="请输入姓名"/></label> </div> <div> <label>年龄: <input ref={this.stuAgeRef} type="text" placeholder="请输入年龄"/></label> </div> <div> <label> 性别: <select ref={this.stuGenderRef}> <option value="男">男</option> <option value="女">女</option> </select> </label> </div> <div> <label>手机: <input ref={this.stuPhoneRef} type="text" placeholder="请输入手机号码"/></label> </div> <button onClick={() => this._dealWithClick()}>创建新用户</button> </fieldset> </div> ); } _dealWithClick() { // 1. 获取用户输入的数据 let name = this.stuNameRef.current.value; let age = this.stuAgeRef.current.value; let gender = this.stuGenderRef.current.value; let phone = this.stuPhoneRef.current.value; // 2. 验证 if (!name.trim() || !age.trim() || !gender.trim() || !phone.trim()) { alert("输入的内容不能为空!") return; } // ... 其他验证 // 3. 调用方法添加数据 this.props.addListToArr({name, age, gender, phone}); // 4. 清空输入框 this.stuNameRef.current.value = ""; this.stuAgeRef.current.value = ""; this.stuPhoneRef.current.value = ""; this.stuGenderRef.current.value = "男"; } } // 子组件——列表 class List extends React.Component { constructor(props) { super(props); } static propTypes = { studentArr: PropTypes.array.isRequired, delFromArr: PropTypes.func.isRequired } render() { const {studentArr, delFromArr} = this.props; return ( <table> <thead> <tr> <td>姓名</td> <td>性别</td> <td>年龄</td> <td>手机</td> <td>删除</td> </tr> </thead> <tbody> { studentArr.map((student, index) => ( <tr key={index}> <td>{student.name}</td> <td>{student.age}</td> <td>{student.gender}</td> <td>{student.phone}</td> <td> <button onClick={() => delFromArr(index)}>删除</button> </td> </tr> )) } </tbody> </table> ); } } ReactDOM.render(<App/>, root); </script> </body> </html>
4. 案例实操2
- Demo: 受控方式实现-用户名和密码获取?
- 核心代码
1. 前言
- 组件的生命周期可以帮助我们清楚的剖析一个组件从创建到销毁的全部流程
- 如果能够做到知其然且知其所以然, 那么在后期多组件、中大型项目开发过程中, 就能够很好的把控项目的性能细节
2. 生命周期
-
图示
-
阶段划分
① 初始化阶段:在组件初始化阶段会执行
图示② 更新阶段:props或state的改变可能会引起组件的更新,组件重新渲染的过程中会调用以下方法
图示③ 卸载阶段:componentWillUnmount()
④ 错误处理:componentDidCatch() -
完整的钩子选项运行流程图
3. 组件运作流程
- 首次初始化渲染: React.render()
- 组件每次发生更新: state: this.setState({})
- 卸载阶段
4. 案例实操
-
Demo1: 组件生命周期钩子函数执行次数分析?
-
Demo2: 年龄增长/删除组件
-
核心代码
<!DOCTYPE html> <html lang="ch"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="root"></div> <script src="./../js/react.development.js"></script> <script src="./../js/react-dom.development.js"></script> <script src="./../js/prop-types.js"></script> <script src="./../js/babel.js"></script> <script type="text/babel"> class Life extends React.Component { // 1. 挂载阶段 constructor(props) { super(props); console.log("1. constructor(props)"); // 状态 this.state = { age: 1 } } UNSAFE_componentWillMount() { console.log("2. componentWillMount()"); } render() { console.log("3. render()"); return ( <div> <h2>I‘m {this.state.age} years old</h2> <button onClick={() => ReactDOM.unmountComponentAtNode(root)}>Stop</button> </div> ) } componentDidMount() { console.log("4. componentDidMount()"); // 开启定时器 this.intervalId = setInterval(() => { console.log("定时器工作中"); // 更新状态 this.setState({ age: this.state.age + 1 }) }, 1000) } // 2. 运行阶段 componentDidUpdate() { console.log("5. componentDidUpdate()"); } // 3. 卸载阶段 componentWillUnmount() { console.log("6. componentWillUnmount()"); // 清除定时器 clearInterval(this.intervalId); } // 4. 错误处理 componentDidCatch() { console.log("componentDidCatch()"); } } ReactDOM.render(<Life/>, root); </script> </body> </html>
5. 辅助阅读——概念:生命周期函数详解
- constructor(props)
① react组件的构造函数, 在挂载之前被调用。在实现React.Component构造函数时,需要先在添加其他内容前,调用super(props),用来将父组件传来的props绑定到这个类中,使用this.props将会得到。
② constructor中应当做些初始化的行为,如:初始化state,将事件处理函数绑定到类实例上,但不要使用setState()。如果没有必要初始化state或绑定方法,则不需要构造constructor,或者把这个组件换成纯函数写法。
③ 可以利用props初始化state,在之后修改state不会对props造成任何修改,但仍然建议提升状态到父组件中,或使用redux统一进行状态管理。
④ 官方建议不要在constructor引入任何具有副作用和订阅功能的代码,这些应当在componentDidMount()中写入。 - getDerivedStateFromProps(nextProps, prevState)
① getDerivedStateFromProps在组件实例化后,或者接受新的props后被调用。他返回一个对象来更新状态,或者返回null表示新的props不需要任何state的更新。
② 如果是由于父组件的props更改,所带来的重新渲染,也会触发此方法。调用steState()不会触发getDerivedStateFromProps()。 - componentWillMount() / UNSAFE_componentWillMount()
① componentWillMount()将在react未来版本中被弃用; UNSAFE_componentWillMount()在组件挂载前被调用,在这个方法中调用setState()不会起作用,因为它在render()前被调用。
② 为了避免副作用和其他的订阅,官方建议使用componentDidMount()代替。这个方法也是用于在服务器渲染上的唯一方法。 - render()
render()方法是必需的。当被调用时,将计算this.props和this.state,并返回以下一种类型:
① React元素, 通过jsx创建,既可以是dom元素,也可以是用户自定义的组件。
② 字符串或数字, 他们将会以文本节点形式渲染到dom中。
③ Portals, react 16版本中提出的新的解决方案,可以使组件脱离父组件层级直接挂载在DOM树的任何位置。
④ null, 什么也不渲染。
⑤ 布尔值, 也是什么都不渲染,通常后跟组件进行判断。
⑥ 当返回null,false,ReactDOM.findDOMNode(this)将会返回null,什么都不会渲染。
注意: render()方法必须是一个纯函数,不能在里面改变state,也不能直接和浏览器进行交互,而是应该将事件放在其他生命周期函数中。 如果shouldComponentUpdate()返回false,render()不会被调用。 - componentWillReceiveProps()
① 官方建议使用getDerivedStateFromProps函数代替componentWillReceiveProps()。
② 当组件挂载后,接收到新的props后会被调用。如果需要更新state来响应props的更改,则可以进行this.props和nextProps的比较,并在此方法中使用this.setState()。如果父组件会让这个组件重新渲染,即使props没有改变,也会调用这个方法。
③ react不会在组件初始化props时调用这个方法, 调用this.setState也不会触发。 - shouldComponentUpdate(nextProps, nextState)
① 调用shouldComponentUpdate, 可以让react知道组件的输出是否受state和props的影响。默认每个状态的更改都会重新渲染,大多数情况下应该保持这个默认行为。在渲染新的props或state前,shouldComponentUpdate会被调用, 默认为true。这个方法不会在初始化时被调用,也不会在forceUpdate()时被调用。返回false不会阻止子组件在state更改时重新渲染。
② 如果shouldComponentUpdate()返回false,componentwillupdate,render和componentDidUpdate不会被调用。在未来版本,shouldComponentUpdate()将会作为一个提示而不是严格的指令,返回false仍然可能导致组件的重新渲染。官方并不建议在shouldComponentUpdate()中进行深度查询或使用JSON.stringify(),他效率非常低,并且损伤性能。 - UNSAFE_componentWillUpdate(nextProps, nextState)
在渲染新的state或props时,UNSAFE_componentWillUpdate会被调用,将此作为在更新发生之前进行准备的机会。这个方法不会在初始化时被调用。不能在这里使用this.setState(),也不能做会触发视图更新的操作。如果需要更新state或props,调用getDerivedStateFromProps。 - getSnapshotBeforeUpdate()
在react render()后的输出被渲染到DOM之前被调用。它让组件能够在它们被潜在更改之前捕获当前值(如滚动位置)。这个生命周期返回的任何值都将作为参数传递给componentDidUpdate()。 - componentDidUpdate(prevProps, prevState, snapshot)
① 调用shouldComponentUpdate, 可以让react知道组件的输出是否受state和props的影响。默认每个状态的更改都会重新渲染,大多数情况下应该保持这个默认行为。在渲染新的props或state前,shouldComponentUpdate会被调用, 默认为true。这个方法不会在初始化时被调用,也不会在forceUpdate()时被调用。返回false不会阻止子组件在state更改时重新渲染。
② 如果shouldComponentUpdate()返回false,componentwillupdate,render和componentDidUpdate不会被调用。在未来版本,shouldComponentUpdate()将会作为一个提示而不是严格的指令,返回false仍然可能导致组件的重新渲染。官方并不建议在shouldComponentUpdate()中进行深度查询或使用JSON.stringify(),他效率非常低,并且损伤性能。
1. 前言
- 虚拟DOM和diff算法是React中非常核心的两个概念, 我们需要对此有一个很全面的认知!
- 这对于我们用脚手架开发项目, 尤其是企业中前后端分离的项目(类似: 后台管理系统)等有很大的帮助!
2. 虚拟DOM
- 内部执行流程
① 用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中;
② 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异;
③ 把步骤2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了。 - 原理剖析
① Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存。可以类比 CPU 和硬盘,硬盘读取速度比较慢,我们会就在它们之间加缓存条;
② 反之, 既然 DOM 运行速度慢,那么我们就在JS 和 DOM 之间加个缓存。JS只操作Virtual DOM,最后的时候再把变更的结果写入DOM。
3. diff算法
- 如果两棵树的根元素类型不同,React会销毁旧树,创建新树
- 对于类型相同的React DOM 元素,React会对比两者的属性是否相同,只更新不同的属性; 当处理完这个DOM节点,React就会递归处理子节点。
- 遍历插入元素, 如果没有key, React将改变每一个子删除重新创建; 为了解决这个问题,React提供了一个 key 属性。当子节点带有key属性,React会通过key来匹配原始树和后来的树。
4. 执行过程
- 通过绑定key, React就知道带有key ‘1024’ 的元素是新的,对于 ‘1025’ 和 ‘1026’ 仅仅移动位置即可。
- key使用注意
5. 九宫格算法案例
-
要求
① 要求: 按照九宫格算法的要求, 添加/删除盒子, 并且做好边界值处理。
② 考察知识点: 1. 虚拟DOM; 2. diff算法; 3. state管理。 -
核心代码
<!DOCTYPE html> <html lang="ch"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .box { width: 320px; height: 600px; background: url("images/bg.jpg") no-repeat; margin: 30px auto; } .top { height: 60px; display: flex; justify-content: space-around; align-items: center; } .top button { border: none; width: 100px; height: 36px; border-radius: 5px; color: #fff; } .top button:first-child { background-color: orange; } .top button:last-child { background-color: orangered; } .bottom { width: 96%; height: 70%; margin-top: 15px; background-color: transparent; position: relative; } .item { display: flex; flex-direction: column; justify-content: center; align-items: center; position: absolute; } span { font-size: 12px; } </style> </head> <body> <div id="root"></div> <div class="main"></div> <script src="./../js/react.development.js"></script> <script src="./../js/react-dom.development.js"></script> <script src="./../js/prop-types.js"></script> <script src="./../js/babel.js"></script> <script type="text/babel"> class Box extends React.Component { constructor(props) { super(props); // 初始化状态 this.state = { // 1.1 数据 dataArr: [ {"icon": "f1", "name": "水果-番茄"}, {"icon": "f2", "name": "水果-苹果"}, {"icon": "f3", "name": "水果-水蜜桃"}, {"icon": "f4", "name": "水果-香蕉"}, {"icon": "f5", "name": "水果-蓝莓"}, {"icon": "f6", "name": "水果-菠萝"}, {"icon": "f7", "name": "水果-草莓"}, {"icon": "f8", "name": "水果-猕猴桃"}, {"icon": "f9", "name": "水果-橙子"} ], // 1.2 水果组件数组 itemArr: [] } } _addItem() { // 1. 变量 let cols = 3, itemWidth = 80, itemHeight = 100, boxWidth = 320, boxHeight = 400; // 2. 求出水平和垂直间距 let xMargin = (boxWidth - cols * itemWidth) / (cols - 1); let yMargin = (boxHeight - cols * itemHeight) / (cols - 1); // 3. 根据水果盒子的下标确定所在的行和列 let index = this.state.itemArr.length; let row = Math.floor(index / cols); let col = index % cols; // 容错处理 if (index >= 9) { alert("已经满了!") return; } // 4. 根据行列求出水果盒子左边和上边间距尺寸 let left = col * (itemWidth + xMargin); let top = row * (itemHeight + yMargin); // 5. 创建组件,装入数组 const {dataArr} = this.state; let itemView = ( <div className="item" key={index} style={{left, top}}> <img src={`images/${dataArr[index].icon}.png`} alt="" style={{width: itemWidth}}/> <span>{dataArr[index].name}</span> </div> ); // 更新状态状态机 let tempArr = this.state.itemArr; tempArr.push(itemView); this.setState({ itemArr: tempArr }) } _removeItem() { let {itemArr} = this.state; if (itemArr.length === 0) { alert("购物车是空的,快去添加吧!"); return; } // 删除 itemArr.pop(); // 更新状态机 this.setState({ itemArr: itemArr }) } render() { return ( <div className="box"> {/*上部分*/} <div className="top"> <button onClick={() => this._addItem()}>添加</button> <button onClick={() => this._removeItem()}>删除</button> </div> {/*下部分*/} <div className="bottom"> {this.state.itemArr} </div> </div> ); } } ReactDOM.render(<Box/>, root); </script> </body> </html>
-
案例运行效果
-
素材
① 数据dataArr: [ {"icon": "f1", "name": "水果-番茄"}, {"icon": "f2", "name": "水果-苹果"}, {"icon": "f3", "name": "水果-水蜜桃"}, {"icon": "f4", "name": "水果-香蕉"}, {"icon": "f5", "name": "水果-蓝莓"}, {"icon": "f6", "name": "水果-菠萝"}, {"icon": "f7", "name": "水果-草莓"}, {"icon": "f8", "name": "水果-猕猴桃"}, {"icon": "f9", "name": "水果-橙子"} ];
② 样式
.box {width: 320px;height: 600px;background: url("images/bg.jpg") no-repeat;margin: 30px auto;} .top {height: 60px;display: flex;justify-content: space-around;align-items: center;} .top button {border: none;width: 100px;height: 36px;border-radius: 5px;color: #fff;} .top button:first-child {background-color: orange;} .top button:last-child {background-color: orangered;} .bottom {width: 96%;height: 70%;margin-top: 15px;background-color: transparent;position: relative;margin-left: 2%;} .item {display: flex;flex-direction: column;justify-content: center;align-items: center;position: absolute;} span{font-size: 12px;}
1. 声明式开发
- jQuery是命令式开发
直接操作DOM,写一个页面要告诉DOM,要如何一步步去操作,大部分都是在写DOM - React和Vue都是声明式开发
面向数据开发,相当于盖房子把图纸画好,React根据图纸帮助我们自动的去构建这座大厦,节约了大量的DOM操作
2. 组件化开发
首字母大写的都是组件
3. 单向数据流
- 图示
- 为什么要用单向数据传递?
防止多个子组件同时修改数据
4. 视图层框架
5. 函数式编程
6. 可以与三方框架并存
- React只负责挂载的DOM节点
- 其它的节点可以运用其它的框架,react不会影响其使用
- 注意:要保证其它框架不会影响React的使用