一、什么是 React?
- 官方解释:用于构建用户界面的 JavaScript 库。是一个将数据渲染为 HTML 视图的开源 JavaScript 库。
- 1.发送请求获取数据
- 2.处理数据(过滤、整理格式等)
- 3.操作 DOM 呈现页面
- 4.babel.min.js 将 jsx 转化为 js;将ES6转化为ES5;(用于将jsx 转为 js)
- 5.react.development.js react核心库;(引用时需先引用核心库,再引用其它周边库)
- 6.react-dom.development.js react扩展库;(用于支持react操作DOM)
(jQuery封装了很多高级操作DOM的方法) - 7.jsx – 使编码人员更加简单的创建虚拟DOM(原始创建虚拟DOM太繁琐);jsx创建虚拟DOM的写法是原始创建虚拟DOM写法的语法糖;
二、为什么要学?
- 原生 JavaScript 操作 DOM 繁琐,效率低;(DOM-API 操作UI)
- 使用 JavaScript 直接操作 DOM ,浏览器会进行大量的重绘重排;
- 原生 JavaScript 没有组件化编码方案,代码复用率低;
三、React 的特点:
- 1.采用组件化模式、声明式编码,提高开发效率及组件复用率;
- 2.在 React Native 中使用 React 语法进行移动端开发;
- 3.使用虚拟 DOM + 优秀的 Diffing 算法,尽量减少与真实 DOM 的交互;
(虚拟DOM优势:不是体现在首次渲染上,而是体现在后期数据量大的时候修改上,虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在用,无需真实DOM上那么多属性)
(关于虚拟DOM:1、本质是Object类型的对象(一般对象);2、虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在用,无需真实DOM上那么多属性;3、虚拟DOM最终会被React转化为真实DOM,呈现在页面上。)
四、React高效的原因
- 1.使用虚拟(virtual)DOM, 不总是直接操作页面真实DOM。
- 2.DOM Diffing算法, 最小化页面重绘。
关于jsx - 全称(JavaScript XML)react 定义的一种类似于 XML 的 JS 扩展语法:JS + XML
- XML早期用于存储和传输数据(后来的JSON使用更方便 — parse(将JSON字符串快速的解析成js里对应的数组和对象)、stringfy(将js里对应的数组和对象转化成JSON字符串));
- 本质是 React.createElement(component,props,…children)方法的语法糖;
- 作用:用来简化创建虚拟DOM;
- 写法:
- 注意:它不是字符串,也不是HTML/XML;
- 注意:它最终产生的是一个JS对象;
- 标签名任意:HTML标签或其他标签;
- js语法用 {} 包裹;
jsx语法规则:
- 1、定义虚拟DOM时,不要写引号;(toLowerCase() 方法将字符串转换为小写,toUpperCase()方法将字符串转换为大写)
- 2、标签中混入 js 表达式时,要用 {};
- 3、样式的类名指定不要用 class,要用 className;
- 4、内联样式,要用
style={ { color: 'white', fontSize: '50px' }}
的形式去写; - 5、只有一个根标签;
- 6、标签必须闭合;
- 7、标签首字母:
(1)、若小写字母开头,则将该标签转为 html 中同名元素;若html中无该标签对应的同名元素,则报错;
(2)、若大写字母开头,react就去渲染对应的组件;若组件没有定义,则报错;
一定注意区分【js语句(代码)】与【js表达式】:- **表达式:**一个表达式会产生一个值,可以放在任何一个需要值的地方;例如:a、a + b、demo(1)、arr.map()、
function test() {}、 - 语句(代码) – 控制代码走向的,没有值: if(){}、for(){}、switch(){case:xxx}
模块与组件、模块化与组件化:
模块: 1. 理解:向外提供特定功能的js程序, 一般就是一个js文件;
2. 为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂;
3. 作用:复用js, 简化js的编写, 提高js运行效率;
**模块化:**当应用的js都以模块来编写的, 这个应用就是一个模块化的应用
组件: 1. 理解:用来实现局部功能效果的代码和资源的集合(html/css/js/image等等);
2. 为什么要用组件: 一个界面的功能更复杂;
3. 作用:复用编码, 简化项目编码, 提高运行效率;
组件化: 当应用是以多组件的方式实现, 这个应用就是一个组件化的应用;
react面向组件化编程:
(1)、**函数式组件:**用函数定义的组件(适用于【简单组件】的定义)
(2)、**类式组件:**用类定义的组件(适用于【复杂组件 — 有状态的组件(state) — 组件的状态里存着数据,数据的改变就会驱动着页面的改变;】的定义)
组件实例的三大核心属性:state 、props、refs- state :state的值是对象,可以包含多个key-value的组合;
组件被称为“状态机”,通过更新组件的state来更新对应的页面显示(重新渲染组件);
强烈注意: (1)、组件中 render 方法中的 this 为组件实例对象;
(2)、组件自定义的方法中 this 为 undefined ,如何解决?
- 强制绑定 this :通过函数对象的 bind()
- 箭头函数
(3)、状态数据,不能直接修改或更新;
state的简写:
- props 当React元素为用户自定义组件时,它会将JSX所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为“props”。
每个组件对象都会有props(properties的简写)属性;组件标签的所有属性都保存在props中;
作用:- 通过标签属性从组件外向组件内传递变化的数据;
- 注意:组件内部不能修改props数据;
批量传递props(批量传递标签属性):
对props进行限制:(对标签属性进行类型的限制、必要性的限制、指定默认值)
注意:props是只读的
props的简写方式:(直接放在类的里面)注意:static的使用
类式组件中的构造器与props: 类中的构造器开发基本都是省略不写
函数式组件使用props:
脚手架中对 props 进行类型和必要性的限制:
- ref 组件内的标签可以定义ref属性来标识自己
(1)、字符串形式的ref — 效率问题,官方不推荐使用,后期可能会被废弃
(2)、回调形式的ref
回调ref中回调次数的问题: 如果 ref 回调函数是以【内联函数】的方式定义的,在【更新】过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定(类绑定)函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。
(3)、createRef
- state :state的值是对象,可以包含多个key-value的组合;
- **表达式:**一个表达式会产生一个值,可以放在任何一个需要值的地方;例如:a、a + b、demo(1)、arr.map()、
React当中的事件处理:
- 通过onXxx属性指定事件处理函数(注意大小写)
1)、 React使用的是自定义(合成)事件, 而不是使用的原生DOM事件 — 为了更好的兼容性
2)、 React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) — 为了高效
2.通过event.target得到发生事件的DOM元素对象 — 不要过度使用ref,有的时候ref是可以避免的;
发生事件的元素正好是要操作的元素,就可以不写ref,而是通过 event.target
非受控组件: 页面内所有输入类DOM的值,如input框、checkbox、radio等,是【现用现取】,那么就是非受控组件;
受控组件: 页面内所有输入类DOM,随着用户的输入将输入的值维护到状态里面去,用的时候直接从状态里面取出来;(类似于Vue里的双向数据绑定)— 受控组件能够省略ref,推荐使用;
高阶函数 — 函数的柯里化:
高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
(1)、若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数;
(2)、若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数;
常见的高阶函数:Promise( new Promise(()=>{}) );setTimeout(()=>{});arr.map()等等;
函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式;
上面的案例不用函数的柯里化实现:
演示函数柯里化:
补充:对象相关的知识:
组件的生命周期:
引出生命周期钩子案例
<!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>引出生命周期</title>
</head>
<body>
<!-- 准备好一个容器 -->
<div id='test'></div>
<!-- 引入react核心库 -->
<script src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script src="../js/babel.min.js"></script>
<script type="text/babel">
// 创建组件
class Life extends React.Component {
// 初始化状态
state = {
opacity: 1 }
// 卸载组件的方法
death = () => {
// 卸载组件
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
// componentDidMount的调用时机:组件挂载页面之后调用
componentDidMount() {
console.log('@'); //只打印一次
// 将定时器挂在组件实例对象自身上
this.timer = setInterval(() => {
// 获取原状态
let {
opacity } = this.state
// 减小0.1
opacity -= 0.1
if (opacity <= 0.1) {
opacity = 1 }
// 设置新的透明度
this.setState({
opacity })
}, 200)
}
// 组件将要卸载
componentWillUnmount() {
// 清除定时器
clearInterval(this.timer)
}
// render的调用时机:初始化渲染、状态更新之后
render() {
console.log('render');
return (
<div>
<h1 style={
{
opacity: this.state.opacity }}>React学不会了怎么办?</h1>
<button onClick={
this.death}>不活了</button>
</div>
)
}
}
// 渲染组件
ReactDOM.render(<Life />, document.getElementById('test'))
</script>
</body>
</html>
生命周期的三个阶段(旧):
1. 初始化阶段: 由ReactDOM.render()触发—初次渲染
1. constructor()
2. componentWillMount()
3. render()
4. componentDidMount() ===》常用,一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息等;
2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发
1. shouldComponentUpdate() 控制组件更新的阀门
2. componentWillUpdate()
3. render()
4. componentDidUpdate()
3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
1. componentWillUnmount() ----》常用,一般在这个钩子中,做一些收尾的事,例如:关闭定时器、取消消息订阅等;
forceUpdate() 不想让状态改变的情况下强制更新
<button onClick={
this.force}>不更改任何状态中的数据,强制更新一下</button>
// 强制更新按钮的回调
force = () => {
this.forceUpdate()
}
在新版本里,componentWillMount、componentWillReceiveProps、componentWillUpdate三个钩子前面需要加上 UNSAFE_
旧的生命周期废弃了:componentWillMount、componentWillReceiveProps、componentWillUpdate;新的生命周期增加了:getDerivedStateFromProps(得到一个派生的状态)、getSnapshotBeforeUpdate(获取更新前的快照)
componentDidUpdate函数可以接收三个参数:
getSnapshotBeforeUpdate() 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给 componentDidUpdate()。
生命周期的三个阶段(新):
1. 初始化阶段: 由ReactDOM.render()触发—初次渲染
- constructor()
- getDerivedStateFromProps
- render()
- componentDidMount()
- 更新阶段: 由组件内部this.setSate()或父组件重新render触发
- getDerivedStateFromProps
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate
- componentDidUpdate()
- 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
- componentWillUnmount()
重要的钩子: - render:初始化渲染或更新渲染调用;
- componentDidMount:开启监听, 发送ajax请求;
- componentWillUnmount:做一些收尾工作, 如: 清理定时器;
即将废弃的勾子: 现在使用会出现警告,下一个大版本需要加上UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用。
1. componentWillMount
2. componentWillReceiveProps
3. componentWillUpdate
getSnapshotBeforeUpdate的使用场景:— 动态新闻列表展示,鼠标滚动到相关位置让它停止滚动
<!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>getSnapshotBeforeUpdate的使用场景</title>
<style>
.list {
width: 200px;
height: 150px;
background-color: skyblue;
overflow: auto;
}
.news {
height: 30px;
}
</style>
</head>
<body>
<!-- 准备好一个容器 -->
<div id='test'></div>
<!-- 引入react核心库 -->
<script src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script src="../js/babel.min.js"></script>
<script type="text/babel">
class NewsList extends React.Component {
state = {
newsArr: [] }
componentDidMount() {
setInterval(() => {
// 获取原状态
const {
newsArr } = this.state
// 模拟一条新闻
const news = '新闻' + (newsArr.length + 1)
// 更新状态
this.setState({
newsArr: [news, ...newsArr] })
}, 1000)
}
getSnapshotBeforeUpdate() {
// 在新的新闻来到之前,获取到当前内容区的高度
return this.refs.list.scrollHeight
}
componentDidUpdate(preProps, preState, height) {
// 新的新闻出现之后的高度-之前的高度
this.refs.list.scrollTop += this.refs.list.scrollHeight - height
}
render() {
return (
<div className="list" ref="list">
{
/*map返回的是数组,map里的return后面跟的是数组中每一项的值*/}
{
this.state.newsArr.map((n, index) => {
return <div key={
index} className="news">{
n}</div>
})}
</div>
)
}
}
ReactDOM.render(<NewsList />, document.getElementById('test'))
</script>
</body>
</html>
React脚手架:
- xxx脚手架: 用来帮助程序员快速创建一个基于xxx库的模板项目;
- 包含了所有需要的配置(语法检查、jsx编译、devServer…);
- 下载好了所有相关的依赖;
- 可以直接运行一个简单效果;
- react提供了一个用于创建react项目的脚手架库: create-react-app;
- 项目的整体技术架构为: react + webpack + es6 + eslint;
- 使用脚手架开发的项目的特点: 模块化, 组件化, 工程化;
创建项目并启动: 第一步,全局安装:npm i -g create-react-app
第二步,切换到想创项目的目录,使用命令:create-react-app hello-react
第三步,进入项目文件夹:cd hello-react
第四步,启动项目:npm start
react脚手架项目结构:
样式的模块化 ---- 防止类名相同产生冲突:
功能界面的组件化编码流程(通用):
1. 拆分组件: 拆分界面,抽取组件;
2. 实现静态组件: 使用组件实现静态页面效果;
3. 实现动态组件:
3.1 动态显示初始化数据
3.1.1 数据类型
3.1.2 数据名称
3.1.2 保存在哪个组件?
3.2 交互(从绑定事件监听开始)
五、todoList案例相关知识点- 1.拆分组件、实现静态组件,注意:className style 的写法;
- 2.动态初始化列表,如何确定将数据放在哪个组件的state中?
* 某个组件使用:放在其自身的state中;
* 某些组件使用:放在它们共同的父组件state中,(官方称此操作为:状态提升) - 3.关于父子之间通信:
* 【父组件】给【子组件】传递数据:通过 props 传递;
* 【子组件】给【父组件】传递数据:通过props传递,要求父提前给子传递一个函数;
- 4.注意 defaultChecked(只有在第一次指定时起作用) 和 checked 的区别,类似的还有:defaultValue 和 value;
checked 必须配合 onChange 来使用,要不然就写死了;
- 5.状态在哪里,操作状态的方法就在哪里;
- 6.鼠标移入移出效果
常用的 ajax 请求库: - jQuery 比较重,如要需要另外引入不建议使用;
- axios 轻量级,建议使用;
* 封装 XmlHttpRequest 对象的 ajax;
* promise 风格;
* 可以用在浏览器端和node服务器端;
脚手架配置代理:
方法一:在 package.json里配置 — 只能配置一个
方法二:配多个 ---- src目录下新建 setupProxy.js文件(该文件只要改了就需要重启脚手架),react会将 setupProxy.js 文件加到 webpack的配置里面,webpack是基于node环境的,用的是commonjs;
六、经典面试题:
(1)react/vue 中的 key 有什么作用?(key 的内部原理是什么?)
(2)为什么遍历列表时,key 最好不要用 index?
1.虚拟 DOM 中 key 的作用:
(1)简单的说:key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用。
(2)详细的说:当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,
随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
若虚拟DOM中内容没变,直接使用之前的真实DOM;
若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM;
b. 旧虚拟DOM中未找到与新虚拟DOM相同的key:
根据数据创建新的真实DOM,随后渲染到页面;
2.用index作为key可能会引发的问题:(建议使用唯一标识id作为key)
a.若对数据进行:逆序添加、逆序删除等破坏顺序的操作:
会产生没有必要的真实DOM更新 》 页面效果没问题,但效率低。
b.如果结构中还包含输入类的DOM:
会产生错误的DOM更细》界面有问题
c.注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序的操作,
仅用于渲染列表用于展示,使用index作为key是没有问题的。
github搜索案例
七、消息订阅与发布
- 工具库: PubSubJS
- 下载: npm install pubsub-js --save
- 使用:
- import PubSub from ‘pubsub-js’ //引入
- PubSub.subscribe(‘delete’, function(data){ }); //订阅
- PubSub.publish(‘delete’, data) //发布消息
fatch发送请求: (不用借助 xhr 发送ajax请求 —用 fatch 发送请求,jQuery 和 axios 都是对 xhr 的封装);
fatch 不是第三方库,而是 windows 内置的,可以不用下载直接使用;也是 promise 风格的;
文档: 1. https://github.github.io/fetch/
2. https://segmentfault.com/a/1190000003810652
特点:1. fetch: 原生函数,不再使用XmlHttpRequest对象提交ajax请求;
2. 老版本浏览器可能不支持;
相关API:
- GET请求
fetch(url).then(function(response) {
return response.json()
}).then(function(data) {
console.log(data)
}).catch(function(e) {
console.log(e)
});
2) POST请求
fetch(url, {
method: "POST",
body: JSON.stringify(data),
}).then(function(data) {
console.log(data)
}).catch(function(e) {
console.log(e)
})
import React, {
Component } from 'react'
// import axios from 'axios'
import PubSub from 'pubsub-js'
export default class Search extends Component {
search = async () => {
// 获取用户的输入(连续解构赋值+重命名---将 value 重命名为 keyWord )
const {
keyWordElement: {
value: keyWord } } = this
console.log(keyWord);
// 发送请求前通知List更新状态
PubSub.publish('atguigu', {
isFirst: false, isLoading: true })
// 发送网络请求 --- 使用axios发送
// axios.get(`http://localhost:3000/api1/search/users?q=${keyWord}`).then(
// response => {
// console.log('成功了', response.data);
// // 请求成功后通知List更新状态
// PubSub.publish('atguigu', { isLoading: false, users: response.data.items })
// },
// error => {
// console.log('失败了', error);
// // 请求失败后通知List更新状态
// PubSub.publish('atguigu', { isLoading: false,