1. React简介
- React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设 Instagram(照片交友) 的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了。
- Angular1 2009 年 谷歌 MVC 不支持 组件化开发
- 由于 React 的设计思想极其独特,属于革命性创新,性能出众,代码逻辑却非常简单。所以,越来越多的人开始关注和使用,认为它可能是将来 Web 开发的主流工具。
- 清楚两个概念:
- library(库):小而巧的库,只提供了特定的API;优点就是 船小好掉头,可以很方便的从一个库切换到另外的库;但是代码几乎不会改变;
- Framework(框架):大而全的是框架;框架提供了一整套的解决方案;所以,如果在项目中间,想切换到另外的框架,是比较困难的;
2. 前端三大主流框架
三大框架一大抄
- Angular.js:出来较早的前端框架,学习曲线比较陡,NG1学起来比较麻烦,NG2 ~ NG5开始,进行了一系列的改革,也提供了组件化开发的概念;从NG2开始,也支持使用TS(TypeScript)进行编程;
- Vue.js:最火(关注的人比较多)的一门前端框架,它是中国人开发的,对我我们来说,文档要友好一些;
- React.js:最流行(用的人比较多)的一门框架,因为它的设计很优秀;
3. React与vue的对比
组件化方面
- 什么是模块化:是从代码的角度来进行分析的;把一些可复用的代码,抽离为单个的模块;便于项目的维护和开发;
- 什么是组件化: 是从 UI 界面的角度 来进行分析的;把一些可服用的UI元素,抽离为单独的组件;便于项目的维护和开发;
- 组件化的好处:随着项目规模的增大,手里的组件越来越多;很方便就能把现有的组件,拼接为一个完整的页面;
-
Vue是如何实现组件化的: 通过
.vue
文件,来创建对应的组件;- template 结构
- script 行为
- style 样式
- React如何实现组件化:大家注意,React中有组件化的概念,但是,并没有像vue这样的组件模板文件;React中,一切都是以JS来表现的;因此要学习React,JS要合格;ES6 和 ES7 (async 和 await) 要会用;
开发团队方面
- React是由FaceBook前端官方团队进行维护和更新的;因此,React的维护开发团队,技术实力比较雄厚;
- Vue:第一版,主要是有作者 尤雨溪 专门进行维护的,当 Vue更新到 2.x 版本后,也有了一个以 尤雨溪 为主导的开源小团队,进行相关的开发和维护;
社区方面
- 在社区方面,React由于诞生的较早,所以社区比较强大,一些常见的问题、坑、最优解决方案,文档、博客在社区中都是可以很方便就能找到的;
- Vue是近两年才火起来的,所以,它的社区相对于React来说,要小一些,可能有的一些坑,没人踩过;
移动APP开发体验方面
- Vue,结合 Weex 这门技术,提供了 迁移到 移动端App开发的体验(Weex,目前只是一个 小的玩具, 并没有很成功的 大案例;)
- React,结合 ReactNative,也提供了无缝迁移到 移动App的开发体验(RN用的最多,也是最火最流行的);
4. 为什么要学习React
- 和Angular1相比,React设计很优秀,一切基于JS并且实现了组件化开发的思想;
- 开发团队实力强悍,不必担心断更的情况;
- 社区强大,很多问题都能找到对应的解决方案;
- 提供了无缝转到 ReactNative 上的开发体验,让我们技术能力得到了拓展;增强了我们的核心竞争力;
- 很多企业中,前端项目的技术选型采用的是React.js;
5. React中几个核心的概念
虚拟DOM(Virtual Document Object Model)
DOM的本质是什么:浏览器中的概念,用JS对象来表示 页面上的元素,并提供了操作 DOM 对象的API;
什么是React中的虚拟DOM:是框架中的概念,是程序员 用JS对象来模拟 页面上的 DOM 和 DOM嵌套;
为什么要实现虚拟DOM(虚拟DOM的目的):为了实现页面中, DOM 元素的高效更新
DOM和虚拟DOM的区别:
DOM:浏览器中,提供的概念;用JS对象,表示页面上的元素,并提供了操作元素的API;
-
虚拟DOM:是框架中的概念;而是开发框架的程序员,手动用JS对象来模拟DOM元素和嵌套关系;
+ 本质: 用JS对象,来模拟DOM元素和嵌套关系;
-
目的:就是为了实现页面元素的高效更新;
-
Diff算法
tree diff:新旧两棵DOM树,逐层对比的过程,就是 Tree Diff; 当整颗DOM逐层对比完毕,则所有需要被按需更新的元素,必然能够找到;
-
component diff:在进行Tree Diff的时候,每一层中,组件级别的对比,叫做 Component Diff;
- 如果对比前后,组件的类型相同,则暂时认为此组件不需要被更新;
- 如果对比前后,组件类型不同,则需要移除旧组件,创建新组件,并追加到页面上;
-
element diff:在进行组件对比的时候,如果两个组件类型相同,则需要进行 元素级别的对比,这叫做 Element Diff;
[图片上传失败...(image-19dc01-1595464286977)]
6. 创建项目
第一种方式
全局安装create-react-app
npm install -g create-react-app
创建项目
create-react-app 项目名
第二种方式
npx
—— 它是 [npm 5.2+ 附带的 package 运行工具],这种方式会先局部安装脚手架,然后在安装项目
npx create-react-app your-app
项目安装这需要等待一段时间,这个过程实际上会安装三个东西
- react: react的顶级库,专门用于创建组件和虚拟DOM的,同时组件的生命周期都在这个包中
- react-dom:专门进行DOM操作的,最主要的应用场景,就是
ReactDOM.render()
- react-scripts: 包含运行和打包react应用程序的所有脚本及配置
7. 创建虚拟dom并渲染
1.创建虚拟DOM元素:
// 这是 创建虚拟DOM元素的 API <h1 title="啊,五环" id="myh1">你比四环多一环</h1>
// 第一个参数: 字符串类型的参数,表示要创建的标签的名称
// 第二个参数:对象类型的参数, 表示 创建的元素的属性节点
// 第三个参数: 子节点
const myh1 = React.createElement('h1', { title: '啊,五环', id: 'myh1' }, '你比四环多一环')
2.渲染:
// 3. 渲染虚拟DOM元素
// 参数1: 表示要渲染的虚拟DOM对象
// 参数2: 指定容器,注意:这里不能直接放 容器元素的Id字符串,需要放一个容器的DOM对象
ReactDOM.render(myh1, document.getElementById('app'))
注:像这样创建虚拟dom太麻烦了,
React.createElement('h1', { title: '啊,五环', id: 'myh1' }, '你比四环多一环')
所以有了jsx 语法,写jsx语法,webpack的babel-loader会转化为这样
React.createElement('h1', { title: '啊,五环', id: 'myh1' }, '你比四环多一环')
8. JSX语法
什么是JSX语法:
就是符合 xml 规范的 JS 语法;(语法格式相对来说,要比HTML严谨很多)
REACT独有的语法 JAVASCRIPT+XML(HTML)
和我们之前自己拼接的HTML字符串类似,都是把HTML结构代码和JS代码或者数据混合在一起了,
但是它不是字符串
下面这些安装,如果用脚手架创建的项目,脚手架已经做了,不用安装了
- 如何启用 jsx 语法?
-
安装
babel
插件- 运行
cnpm i babel-core babel-loader babel-plugin-transform-runtime -D
- 运行
cnpm i babel-preset-env babel-preset-stage-0 -D
- 运行
-
安装能够识别转换jsx语法的包
babel-preset-react
- 运行
cnpm i babel-preset-react -D
- 运行
-
添加
.babelrc
配置文件{ "presets": ["env", "stage-0", "react"], "plugins": ["transform-runtime"] }
-
添加babel-loader配置项:
module: { //要打包的第三方模块 rules: [ { test: /\.js|jsx$/, use: 'babel-loader', exclude: /node_modules/ } ] }
jsx 语法的本质:并不是直接把 jsx 渲染到页面上,而是 内部先转换成了 createElement 形式,再渲染的;
-
在 jsx 中混合写入 js 表达式:在 jsx 语法中,要把 JS代码写到
{ }
中,但是要求JS代码指执行完成有返回结果(JS表达式)- 渲染数字
- 渲染字符串
- 渲染布尔值
- 为属性绑定值
- 渲染jsx元素
- 渲染jsx元素数组
在 jsx 中 写注释:推荐使用
{ /* 这是注释 */ }
为 jsx 中的元素添加class类名:需要使用
className
来替代class
;htmlFor
替换label的for
属性在JSX创建DOM的时候,所有的节点,必须有唯一的根元素进行包裹;
在 jsx 语法中,标签必须 成对出现,如果是单标签,则必须自闭和!
当 编译引擎,在编译JSX代码的时候,如果遇到了
<
那么就把它当作 HTML代码去编译,如果遇到了
{}
就把 花括号内部的代码当作 普通JS代码去编译;
9. React中创建组件
不管是VUE还是REACT框架,设计之初都是期望我们按照“组件/模块管理”的方式来构建程序的,也就是把一个程序划分为一个个的组件来单独处理
优势
1.有助于多人协作开发
2.我们开发的组件可以被复用REACT中创建组件有两种方式:
函数声明式组件
基于继承COMPONENT类来创建组件SRC -> COMPONENT :这个文件夹中存放的就是开发的组件
返回的jsx,必须是唯一的节点包裹,如果不想让这个节点展示,可以用这个包裹 <> <h1>hhh</h1>
<h1>hhh</h1> </>
第1种 - 函数声明式组件(无状态组件)
函数声明式组件,如果要接收外界传递的数据,需要在 构造函数的参数列表中使用
props
来接收;必须要向外return一个合法的JSX;
-
创建组件:
//Dialog.js import React from 'react'; //=>每一个组件中都要导入REACT,因为需要基于它的CREATE-ELEMENT把JSX进行解析渲染呢 /* * 函数式声明组件 * 1.函数返回结果是一个新的JSX(也就是当前组件的JSX结构) * 2.PROPS变量存储的值是一个对象,包含了调取组件时候传递的属性值(不传递是一个空对象) * 3.children 代表双闭合组件中的子元素,相当于vue中插槽的概念 * */ export default function Dialog(props) {//props形参,接收外界传递过来的数据,只读的;不能被重新赋值; let {con, lx = 0, style = {},children} = props, //=>children(代表双闭合组件中的子元素) return <section style={style}> <h2>{title}</h2> <div>{con}</div> {children} </section>; };
-
使用组件,并为组件传递数据:
import React from 'react'; import ReactDOM from 'react-dom';
import Dialog from './component/Dialog'; //引入组件,组件的名称首字母必须是大写
ReactDOM.render(<div>
{/注释:JSX中调取组件,只需要把组件当做一个标签调取使用即可(单闭合和双闭合都可以)/}
<Dialog con='哈哈哈' style={{color: 'red'}}/>
{/*属性值不是字符串,我们需要使用大括号包起来*/}
<Dialog con='嘿嘿嘿' lx={1} style={{color: 'red'}}>
<span>1</span> //这个双闭合标签里面的内容就是用children接受
<span>2</span>
</Dialog>
</div>, root);
### 第2种 - 创建类式(有状态组件)
>必须继承 React.Component 和有一个render函数并且返回jsx
* 创建组件
```js
import React from 'react'
export default class Dialog extends React.Component {
render() {
//“this.props组件的属性是只读的”
return <section>
<h3>{this.props.lx}</h3>
<div>{this.props.con}</div>
</section>;
}
}
-
使用组件
import React from 'react'; import ReactDOM from 'react-dom'; import Dialog from './01-component/Dialog' //引入组件 ReactDOM.render( <Dialog con='哈哈哈' lx={10}/>, //使用组件 document.getElementById('root') );
两种创建组件方式的对比
- 用函数创建出来的组件:叫做“无状态组件”
- 用class关键字创建出来的组件:叫做“有状态组件”
- 用函数**创建出来的组件:叫做“无状态组件”【无状态组件今后用的不多】
- 用class关键字创建出来的组件:叫做“有状态组件”【今后用的最多】
- 什么情况下使用有状态组件?什么情况下使用无状态组件?
- 如果一个组件需要有自己的私有数据,则推荐使用:class创建的有状态组件;
- 如果一个组件不需要有私有的数据,则推荐使用:无状态组件;
- React官方说:无状态组件,由于没有自己的state和生命周期函数,所以运行效率会比 有状态组件稍微高一些;
有状态组件和无状态组件之间的本质区别就是:有无state属性、和 有无生命周期函数;
10.组件的数据挂载方式
属性(props)
props
是正常是外部传入的,属性不能被组件自己更改,组件内部也可以设置默认值,字符类型,属性是描述性质、特点的,组件自己不能随意更改。
在使用一个组件的时候,可以把参数放在标签的属性当中,所有的属性都会作为组件
props
对象的键值。通过箭头函数创建的组件,需要通过函数的参数来接收props
给属性设置规则,需要安装第三方插件
yarn add prop-types
给属性设置默认值和规则
import React from 'react'
import PropTypes from 'prop-types';
export default class Dialog extends React.Component {
//THIS.PROPS是只读的,我们无法在方法中修改它的值,但是可以给其设置默认值
static defaultProps = {
lx: '系统提示'
};
//PROP-TYPES是FACEBOOK公司开发的一个插件
基于这个插件我们可以给组件传递的属性设置规则
static propTypes = {
//=>不仅传递的内容是字符串,并且还必须传递
con: PropTypes.string.isRequired,
//=>传递的内容需要是数字
lx: PropTypes.number,
//=>传递的内容需要是数组
city:PropTypes.array,
//=>数组中的每一项需要是字符串
list:PropTypes.arrayOf(PropTypes.string),
//=>自定义函数验证
address:function(props,propname){
if(props[propname] !=='aa'){
return new Error('内容非法')
}
//console.log(arguments);
}
};
render() {
//“this.props组件的属性是只读的”
return <section>
<h3>{this.props.lx}</h3>
<div>{this.props.con}</div>
</section>;
}
}
状态state
react中的state相当于vue中的data属性
state顾名思义就是状态,它只是用来控制这个组件本身自己的状态,我们可以用state来完成对行为的控制、数据的更新、界面的渲染,由于组件不能修改传入的props,所以需要记录自身的数据变化。
更改状态,<span style="color:red">只能用 setState()方法</span>,当我们调用这个函数的时候,<span style="color:red">React.js 会更新组件的状态 state ,并且重新调用 render 方法</span>,然后再把 render 方法所渲染的最新的内容显示到页面上
setState 本身在生命周期函数或者在合成事件中执行是异步的
在保证生命周期函数执行的顺序不紊乱
保证其实现渲染队列的机制(可以合并setState统一渲染)
在原生的事件绑定中和其他异步操作中的set-state是同步的,没有渲染队列效果了
import React, { Component } from 'react'
export default class movie extends Component {
constructor(){
super()
//state定义的第一种方式
//设置状态
this.state = {
title:"中国机长"
}
}
//state定义的第二种方式
// state ={
// title:"中国机长",
// count:1
// }
componentDidMount(){
//更改状态,因为是异步的,想立即获取更改后的内容,可以用第2个参数:回调函数
//更改state第一种方式
this.setState({
title:"战狼"
},() => {
//相当于vue $nextTick
console.log(this.state.title);
}
)
//更改state的第二种方式
//this.state.title="战狼"
//this.setState({})
}
render() {
return (
<div>
{this.state.title}
</div>
)
}
}
组件中的 props
和 state/data
之间的区别
- props 中的数据都是外界传递过来的;
- state/data 中的数据,都是组件私有的;(通过 Ajax 获取回来的数据,一般都是私有数据);
- props 中的数据都是只读的;不能重新赋值;
- state/data 中的数据,都是可读可写的;
状态提升
如果有多个组件共享一个数据,把这个数据放到共同的父级组件中来管理
受控组件与非受控组件
受控组件
其实就相当于vue中的v-model指令,在react中是自己手动实现,用onChange事件
<span style="color:red">双向数据绑定就是受控组件</span>
受控组件就是可以被 react 状态控制的组件
在 react 中,Input textarea 等组件默认是非受控组件(输入框内部的值是用户控制,和React无关)。但是也可以转化成受控组件,<span style="color:red">就是通过 onChange 事件获取当前输入内容,将当前输入内容作为 value 传入,此时就成为受控组件</span>。
好处:可以通过 onChange 事件控制用户输入,过滤不合理输入。
import React, { Component} from 'react'; export default class Form extends Component { constructor() { super(); this.state = { name: "", desc: "", } } handleChange(event) { this.setState({ [event.target.name]: event.target.value //把表单中的值赋值给状态 }) } render() { return ( <div>//表单中的value,绑定了state上的状态值 <input type="text" value={this.state.name} name="name" onChange={this.handleChange.bind(this)} /> <textarea value={this.state.desc} name="desc" onChange={this.handleChange.bind(this)}> </textarea> </div> ); } }
非受控组件
不受状态控制的就是非受控组件
基于REF操作DOM实现视图更新的,叫做“非受控组件”
使用场景,必须手动操作DOM元素,setState 实现不了的,例如文件上传 <input type=file>
import React,{Component} from 'react'; import ReactDOM from 'react-dom'; export default class Sum extends Component{ handleChange=(event)=>{ let a = parseInt(this.refs.a.value||0); let b = parseInt(this.refs.b.value||0); this.refs.result.value = a+b; } render(){ return ( //经过React封装可以onChange可以写在div上 <div onChange={this.handleChange}> <input type="text" ref="a" /> + <input type="text" ref="b" /> = <input type="text" ref="result" /> </div> //input是非受控组件,因为不受状态控制 ) } }
11.组件中DOM样式
-
行内
import React from 'react' export default class Movie extends React.Component { render() { return <div> {/* 行内样式 */} <h1 style={{ fontSize: '20px', color: 'red' }}>正在热映</h1> </div> } }
-
外部引入
<span style="color:red">这种引入方式是全局生效</span>
import React from 'react' import './Movie.css' //从外部引入 export default class Movie extends React.Component { render() { return <div className="wrap"> <h1>正在热映</h1> </div> } }
-
模块化
<span style="color:red">这种引入方式是局部生效</span>,目的解决全局冲突
-
Vue 组件中的样式表,有没有 冲突的问题???
Vue 组件中的样式表,也有冲突的问题;但是,可以使用 <style scoped></style>
React 中,有没有类似于 scoped 这样的指令呢?
没有;因为 在 React 中,根本就没有指令的概念;
模块块就是为了解决冲突的问题,局部生效
文件名必须是 *.module.css结尾,同样的,如是要是
.sass
或.scss
的话,文件名格式应该是[name].module.sass
或[name].module.scss
其实模块化,就是webpack给css-loader配置了参数modules
module: { rules: [ // 其中,有个固定的参数,叫做 modules , 表示为 普通的 CSS 样式表,启用模块化 { test: /\.scss$/, use: ['style-loader', 'css-loader?modules', 'sass-loader'] } ] }
import React from 'react' import common from "./common.module.css" //引入css文件 export default class Movie extends React.Component { render() { return <div> <h1 className={common.error}> //使用css gggg </h1> </div> } }
//common.module.css 文件 .error{ color:red; font-size: 30px; }
-
-
不同的条件添加不同的样式
- 安装
yarn add classnames
特点就是根据动态数据控制样式
局部,不会全局污染
代码:
import React from 'react' import classNames from 'classnames/bind' //引入第三方插件 import styles from './styles.css' //引入样式 let cx = classNames.bind(styles) //绑定在一块 export default class profile extends React.Component{ render(){ let names = cx({ inProcess:true, //根据数据来决定使用哪一个样式 error:false }) return <div> <h1 className={names}>gp18</h1> </div> } }
//styles.css文件 .inProcess { color: orange; font-size: 30px; } .error { color: red; font-size: 30px; }
-
css-in-js
styled-components`是针对React写的一套css-in-js框架,简单来讲就是在js中写css
解决全局污染
安装
yarn add styled-components
代码:
import React from 'react' import {Wrap} from './search-style' //引入样式 export default class Search extends React.Component{ render(){ return <Wrap color="red"> //使用 yyyy <h1>hhh</h1> </Wrap> } }
//search-style.js 文件 import React from 'react' import styled from 'styled-components' //引入第三方插件 const Wrap = styled.div` width:500px; height:500px; background:${(props)=>props.color}; //动态赋值 font-size:30px; h1 { font-size:50px; } ` export { Wrap }
12.事件处理
采用on+事件名的方式来绑定一个事件,注意,这里和原生的事件是有区别的,原生的事件全是小写
onclick
, React里的事件是驼峰onClick
,React的事件并不是原生事件,而是合成事件。Event对象
和普通浏览器一样,事件handler会被自动传入一个
event
对象,这个对象和普通的浏览器event
对象所包含的方法和属性都基本一致。不同的是 React中的event
对象并不是浏览器提供的,而是它自己内部所构建的。它同样具有event.stopPropagation
、event.preventDefault
这种常用的方法
-
事件的几种写法,目的能访问到this
- 第一种写法 <span style="color:red"> (不能传递参数)</span>
import React, { Component } from 'react' export default class event1 extends Component { constructor() { super() this.state = { name: 'gp18' } } handleClick=(event) => { //箭头函数 console.log(this.state.name); } render() { return ( <div> /*这种绑定事件的方法,要想this能访问到, handleClick函数必须用箭头函数 */ <button onClick={this.handleClick}>add</button> </div> ) } }
- 第二种写法 <span style="color:red"> (不能传递参数)</span>
import React, { Component } from 'react' export default class event1 extends Component { constructor() { super() this.state = { name: 'gp18' } //@key this.myhandleclick = this.handleClick.bind(this) } //@key 最后追加一个参数,即可接受event handleClick(val, event) { console.log(val, event, this.state.name); } render() { return ( <div> <button onClick={this.myhandleclick} //@key >添加</button> </div> ) } }
- 第三种写法 <span style="color:red"> (可以传递参数)</span>
import React, { Component } from 'react' export default class event1 extends Component { constructor() { super() this.state = {name: 'gp18'} } //触发函数 handleClick(val,event){ console.log(this.state.name,val,event); } render() { return ( <div> //因为handleClick函数不是箭头函数,需要改变this指向 <button onClick={this.handleClick.bind(this,'abc')} >添加</button> </div> ) } }
- 第四种写法 <span style="color:red"> (可以传递参数)</span>
import React, { Component } from 'react' export default class event1 extends Component { constructor() { super() this.state = {name: 'gp18'} } handleClick(val,event){ console.log(this.state.name,val,event); } render() { return ( <div> <button onClick={ (event) =>{return this.handleClick('abc',event)} }>添加</button> </div> ) } }
13. 组件生命周期
- 生命周期的概念
每个组件的实例,从创建、到运行、直到销毁,在这个过程 中,会出发一些列 事件,这些事件就叫做组件的生命周期函数;
生命周期分为三部分:
组件创建阶段
组件创建阶段的生命周期函数,有一个显著的特点:创建阶段的生命周期函数,在组件的一辈子中,只执行一次;
- componentWillMount
组件将要被挂载,此时还没有开始渲染虚拟DOM
- render:
第一次开始渲染真正的虚拟DOM,当render执行完,
内存 中就有了完整的虚拟DOM了
- componentDidMount
组件完成了挂载,此时,组件已经显示到了页面上,
当这个方法执行完,组件就进入都了 运行中 的状态
如果要初始化第三方的dom库,也在这里进行初始化。只有到这里才能获取到真实的dom.
通常在这里进行ajax请求
- 组件运行阶段
也有一个显著的特点,根据组件的state和props的改变,有选择性的触发0次或多次;
- componentWillReceiveProps:
组件将要接收新属性,此时,只要这个方法被触发,
就证明父组件为当前子组件传递了新的属性值;
props改变的时候
- shouldComponentUpdate:
组件是否需要被更新,此时,组件尚未被更新,
但是,state 和 props 肯定是最新的
在渲染新的
props
或state
前,shouldComponentUpdate
会被调用。默认为true
。 这个方法不会在初始化时被调用,也不会在
forceUpdate()
时被调用。 返回
false
不会阻止子组件在state
更改时重新渲染。 如果
shouldComponentUpdate()
返回false
,componentWillUpdate
,render
和componentDidUpdate
不会被调用。
- componentWillUpdate:
组件将要被更新,此时,尚未开始更新,
内存中的虚拟DOM 树还是旧的
- render:
此时,又要重新根据最新的 state 和 props
重新渲染一棵内存中的 虚拟DOM树,当 render 调用完毕,内存中的旧DOM树,
已经被新DOM树替换了!此时页面还是旧的
- componentDidUpdate
此时,页面又被重新渲染了,state 和 虚拟DOM 和页面已经 完全保持同步
- 组件销毁阶段
也有一个显著的特点,一辈子只执行一次;
- componentWillUnmount:
组件将要被卸载,此时组件还可以正常使用;
在组件被卸载并销毁之前立即被调用。在此方法中执行任何必要的清理,
例如使定时器无效,取消网络请求或清理在
componentDidMount
中创建的任何监听
-
生命周期图
新版本v16.4 的生命周期函数
- v17版本
componentWillMount,componentWillReceiveProps,componentWillUpdate这三个函数将要作废
v16.4新增的生命周期函数
getDerivedStateFromProps
//可以拿到父组件传递过来的属性,同时可以拿到当前组件的state //可以把传递过来的属性合并到当前组件state上 static getDerivedStateFromProps(props, state) { console.log('getDerivedStateFromProps:', props, state) return { ...props, ...state } }
getSnapshotBeforeUpdate()
在react
render()
后的输出被渲染到DOM之前被调用。它使您的组件能够在它们被潜在更改之前捕获当前值(如滚动位置)。这个生命周期返回的任何值都将作为参数传componentDidUpdate()只要写这个生命周期函数就必须写componentDidUpdate
getSnapshotBeforeUpdate(prevProps, prevState) { console.log("getSnapshotBeforeUpdate:", prevProps, prevState) return { username:'hanye', age:20 }; } componentDidUpdate(prevProps, prevState,sanpshot){ console.log("componentDidUpdate:",prevProps, prevState,sanpshot) }
PureComponent
PureComponnet
里如果接收到的新属性或者是更改后的状态和原属性、原状态相同的话,就不会去重新render了
在里面也可以使用shouldComponentUpdate
,而且。是否重新渲染以shouldComponentUpdate
的返回值为最终的决定因素。此方法仅作为性能优化的方式而存在。不要企图依靠此方法来“阻止”渲染,因为这可能会产生 bug。你应该**考虑使用内置的 [PureComponent
](当 props 或 state 发生变化时,shouldComponentUpdate()
会在渲染执行之前被调用。返回值默认为 true。首次渲染或使用forceUpdate()
时不会调用该方法。)import React, { Component,PureComponent } from 'react'; export default class Parent extends PureComponent { //@Key constructor() { super(); this.state = { count: 1,} } render() { return ( <div id="app"> <button onClick={this.handleClick}> 强制更新 </button> </div> ); } handleClick = () => { this.state.count = 100; this.forceUpdate(); //@key } }
14 HOC高阶组件
- 定义
具体而言,高阶组件是参数为组件,返回值为新组件的函数
const EnhancedComponent = higherOrderComponent(WrappedComponent);
- 作用
在多个不同的组件中需要用到相同的功能,这个解决方法,通常是高阶组件。
- 例子
//定义一个高阶组件 Hoc.js import React, { Component } from 'react' const Hoc = (WrapperCommonent)=>{ return class Copyright extends Component { render() { return ( <div> <WrapperCommonent></WrapperCommonent> <div>版权所有</div> </div> ) } } } export default Hoc //使用高阶组件 import React, { Component } from 'react' import Hoc from './Hoc' class Base extends Component { render() { return ( <div> react.js 是一个构建用户节目的库 </div> ) } } export default Hoc(base) //@Key
15.组件通信
父组件与子组件通信
父组件将自己的状态传递给子组件,子组件当做属性来接收,当父组件更改自己状态的时候,子组件接收到的属性就会发生改变
//父组件 import React, { Component } from 'react' import Child from './Child' export default class Parent extends Component { constructor(){ super() this.state = {count:1} } render() { return ( <Child count={this.state.count}></Child> //状态传递给子组件 ) } } //子组件 import React, {Component} from 'react' export default class Child extends Component { render() { return <div> {this.props.count} //子组件当做属性来接收 </div> } }
子组件与父组件通信
父组件将自己的某个方法传递给子组件,在方法里可以做任意操作,比如可以更改状态,子组件通过
this.props
接收到父组件的方法后调用。//父组件 import React, { Component } from 'react' import Child from './Child' export default class Parent extends Component { constructor(){ super() this.state = {count:1} } handclick=()=>{ this.setState({ count:10 }) } render() { return ( <div> {this.state.count} <Child fn={this.handclick}></Child> //将方法传递给子组件 </div> ) } } //子组件 import React, {Component} from 'react' export default class Child extends Component { componentDidMount(){ this.props.fn() } render() { return <div> child </div> } }
跨组件通信 context
在react没有类似vue中的事件总线来解决这个问题,我们只能借助它们共同的父级组件来实现,将非父子关系装换成多维度的父子关系。react提供了
context
api来实现跨组件通信, React 16.3之后的context
api较之前的好用。在父组件上提供数据,其他后代组件,都能访问到该数据
举个计数器的例子,这个例子是context在实际项目这样写,具体基础的看文档
//Mycontext.js import React, { Component,createContext} from 'react' const {Provider,Consumer:MyConsumer} = createContext() class MyProvider extends Component { constructor(){ super() this.state = {count:1} } incr=()=>{ this.setState({ count:this.state.count+1 }) } render() { return ( //需要共享的数据写在value里 <Provider value={{...this.state,'incr':this.incr,'decr':this.decr}}> {this.props.children} </Provider> ) } } export {MyProvider,MyConsumer} //父组件 Parent.js import React, { Component } from 'react' import Child from './Child' import {MyProvider} from './Mycontext' export default class Parent extends Component { render() { return ( <MyProvider> <Child></Child> </MyProvider> ) } } //后代组件 Child.js import React, { Component } from 'react' import {MyConsumer} from './Mycontext' export default class Button extends Component { render() { return (//后代访问共享数据 <MyConsumer> { ({incr,count})=>{ return <button onClick={incr}>add按钮:{count}</button> } } </MyConsumer> ) } }
redux
什么是redux?
进行状态统一管理的类库(适应于任何技术体系的项目)
1.只要两个或多个组件之间想要实现信息的共享,都可以基于redux解决,把共享的信息存储到redux容器进行管理
2.还可以做临时存储,页面加载的时候,把从服务器获取的数据信息存储到redux中
为什么要用redux
因为对于react来说,同级组件之间的通信尤为麻烦,或者是非常麻烦了,所以我们把所有需要多个组件使用的state拿出来,整合到顶部容器,进行分发
需要使用redux的项目
- 用户的使用方式复杂
- 不同身份的用户有不同的使用方式(比如普通用户和管理员)
- 多个用户之间可以协作
- 与服务器大量交互,或者使用了WebSocket
- View要从多个来源获取数据
从组件层面考虑,什么样子的需要Redux:
- 某个组件的状态,需要共享
- 某个状态需要在任何地方都可以拿到
- 一个组件需要改变全局状态
- 一个组件需要改变另一个组件的状态
Redux的设计思想
- Web 应用是一个状态机,视图与状态是一一对应的。
- 所有的状态,保存在一个对象里面(唯一数据源)
Redux的使用的三大原则
- Single Source of Truth(唯一的数据源)
- State is read-only(状态是只读的)
- Changes are made with pure function(数据的改变必须通过纯函数完成)
Redux的流程
Store的角色是整个应用的数据存储中心,集中大部分页面需要的状态数据;
ActionCreators ,view 层与data层的介质;
Reduce ,接收action并更新Store。
所以流程是 用户通过界面组件 触发ActionCreator,携带Store中的旧State与Action 流向Reducer,Reducer返回新的state,并更新界面。自己实现的redux
<!DOCTYPE html> <html lang="en"> <body> <button onclick="store.dispatch({type:'DECR',num:1})">-</button> <h1 id="count"></h1> <button onclick="store.dispatch({type:'INCR',num:1})">+</button> <script> //集中管理state,和dispatch,action const createStore = (reducer) => { let state; const getState = () => state //定义一个监听器队列,管理所有订阅的方法 const listeners = [] const subscribe = (listener) => listeners.push(listener) const dispatch = (action) => { //1.执行REDUCER,修改容器中的状态信息 state = changeState(state, action) //2.容器中状态信息经过REDUCER修改后,通知事件池中的方法依次执行 listeners.forEach((item) => { item() }) } //=>创建容器的时候执行一次DISPATCH, //=>目的是把REDUCER中的默认状态信息赋值给REDUX容器中的状态 dispatch({ type: '$$INIT_DEFAULT_STATE' }); return { getState, subscribe, dispatch } } //其实这个方法就是写自己的逻辑的地方 const changeState = (state = { count: 0 }, action) => { switch (action.type) { case "INCR": return { count: state.count + action.num } case "DECR": return { count: state.count - action.num } default: return state } } const store = createStore(changeState) store.subscribe(render) function render() { document.getElementById("count").innerHTML = store.getState().count; } render() </script> </body> </html>
react-redux
安装
yarn add redux react-redux yarn add redux-thunk //异步请求数据要用到这个插件
redux
适应于所有项目,store = createStore(reducer),然后在需要的地方通过store.getState()去获取数据,通过store.dispatch去更新数据,通过store.subscribe去订阅数据变化然后进行setState...如果很多地方都这样做一遍,实在是不堪其重
REACT-REDUX
描述
react-redux只是适应react项目
是把REDUX进一步封装,适配REACT项目,让REDUX操作更简洁
STORE文件夹中的内容和REDUX一模一样
在组件调取使用的时候可以优化一些步骤
相对于传统的REDUX,我们做的步骤优化
导出的不在是我们创建的组件,而是基于CONNECT构造后的高阶组件
export default connect([mapStateToProps], [mapDispatchToProps])([自己创建的组件]);
REACT-REDUX帮我们做了一件非常重要的事情:
以前我们需要自己基于SUBSCRIBE向事件池追 加方法,以达到容器状态信息改变,
执行我们追加的方法,重新渲染组件的目的,但是现在不用了,
REACT-REDUX帮我们做了这件事:“所有用到REDUX容器状态信息的组件,都会向事件池 中追加一个方法,当状态信息改变,通知方法执行,
把最新的状态信息作为属性传递给组件,组件的属性值改变了,组件也会重新渲染”
react-redux 其实就提供了这四个东西
Provider 根组件
当前整个项目都在Provider组件下
作用就是把创建的STORE可以供内部任何后代组件使用(基于上下文完成的)
=>Provider组件中只允许出现一个子元素
=>把创建的STORE基于属性传递给Provider(这样后代组件中都可以使用这个STORE了)
redux的时候,每个组件想使用store,属性都得传store
这个函数的底层实现原理
其实就是根据上下文实现的
PROVIDER:当前项目的“根”组件
接收通过属性传递进来的STORE,把STORE挂载到上下文中,
这样当前项目中任何一个组件中,想要使用REDUX中的STORE,
直接通过上下文获取即可
class Provider extends React.Component { //=>设置上下文信息类型 static childContextTypes = { store: PropTypes.object }; //=>设置上下文信息值 getChildContext() { return { store: this.props.store }; } constructor(props, context) { super(props, context); } render() { return this.props.children; } }
- connect 高阶组件
CONNECT:高阶组件(基于高阶函数:柯理化函数)创建的组件就是高阶组件 @PARAMS mapStateToProps:回调函数,把REDUX中的部分状态信息挂载到指定组件的属性上
function mapStateToProps(state){ //=>state:REDUX容器中的状态信息 return {}; //=>RETURN对象中有啥,就把啥挂载到属性上 } ``` mapDispatchToProps:回调函数,把一些需要派发的任务方法也挂载到组件的属性上 ``` function mapDispatchToProps(dispatch){ //=>dispatch:store中的dispatch return { init(){ dispatch({...}); } }; //=>RETURN啥就把啥挂载到属性上(返回的方法中有执行dispatch派发任务的操作) } ``` @RETURN 返回一个新的函数 CONNECT-HOT
====
CONNECT-HOT
@PARAMS
传递进来的是要操作的组件,我们需要把指定的属性和方法都挂载到当前组件的属性上@RETURN
返回一个新的组件Proxy(代理组件),在代理组件中,我们要获取Provider在上下文中存储的store,紧接着获取store中的state和dispatch,把mapStateToProps、mapDispatchToProps回调函数执行,接收返回的结果,在把这些结果挂载到Component这个要操作组件的属性上function connect(mapStateToProps, mapDispatchToProps) {
return function connectHOT(Component) {
return class Proxy extends React.Component {
//=>获取上下文中的STORE
static contextTypes = {
store: PropTypes.object
};constructor(props, context) { super(props, context); this.state = this.queryMountProps(); } //=>基于REDUX中的SUBSCRIBE向事件池中追加一个方法,当容器中状态改变,我们需要重新获取最新的状态信息,并且重新把COMPONENT渲染,把最新的状态信息通过属性传递给COMPONENT componentDidMount() { this.context.store.subscribe(() => { this.setState(this.queryMountProps()); }); } //=>渲染COMPONENT组件,并且把获取的信息(状态、方法)挂载到组件属性上(单独调取POXY组件的是时候传递的属性也给COMPONENT) render() { return <Component {...this.state} {...this.props}/> } //=>从REDUX中获取最新的信息,基于回调函数筛选,返回的是需要挂载到组件属性上的信息 queryMountProps = () => { let {store} = this.context, state = store.getState(); let propsState = typeof mapStateToProps === 'function' ? mapStateToProps(state) : {}; let propsDispatch = typeof mapDispatchToProps === 'function' ? mapDispatchToProps(store.dispatch) : {}; return { ...propsState, ...propsDispatch }; }; } }
}
- mapStateToProps函数
let mapStateToProps = state => { //=>state:就是REDUX容器中的状态信息 //=>我们返回的是啥,就把它挂载到当前组件的属性上 //=>(REDUX存储很多信息,我们想用啥就返回啥即 可) return { ...state.vote }; };
- mapDispatchToProps
//=>把REDUX中的DISPATCH派发行为遍历,也赋值给组件的属性(ActionCreator) let mapDispatchToProps = dispatch => { //=>dispatch:STORE中存储的DISPATCH方法 //=>返回的是啥,就相当于把啥挂载到组件的属性上 //(一般我们挂载一些方法,这些方法中完成了DISPATCH派发任务操作) return { init(initData) { //action.vote.init(initData)返回的就是类似{type:'aa',...} dispatch(action.vote.init(initData)); } }; }; export default connect(state => ({...state.vote}), action.vote)(VoteBase);//=>REACT-REDUX帮我们做了一件事情,把ACTION-CREATOR中编写的方法(返回ACTION对象的方法),自动构建成DISPATCH派发任务的方法,也就是mapDispatchToProps这种格式
具体使用
目录
store
reducer 存放每一个模块的reducer
vote.js
personal.js
...
index.js 把每一个模块的reducer最后合并成为一个reducer
action 存放每一个模块需要进行的派发任务(ActionCreator)
vote.js
personal.js
...
index.js 所有模块的ACTION进行合并
action-types.js 所有派发任务的行为标识都在这里进行宏观管理
index.js 创建STORE
代码
reducer目录的代码
action目录的代码
action-types.js 文件
index.js 文件
使用
在入口index.js提供数据store
- 在子组件消费数据
16 路由
安装
react-router-dom
第一个例子:基本路由
基本使用
路由跳转
匹配不到返回404
两种方式的传参及接受参数
使用component属性渲染组件
import React, { Component } from 'react' import {BrowserRouter as Router,Route,Link,useLocation,Redirect,Switch} from 'react-router-dom' function Home(props) { //获取参数 let result = new URLSearchParams(useLocation().search) console.log(result.get("city")); return <h2>Home</h2> } function About(props) { //获取参数 console.log(props.match.params.id); return <h2>About</h2>; } export default class index extends Component { render() { return ( <Router> //必须用Router包裹 <Link to="/">首页</Link> <Link to="/about/66">关于</Link> <Link to="/my">我的</Link> <hr/> <Switch> //Switch:排他性,只能匹配到一个路由 <Route exact path="/index/" component={Home}></Route> <Route path="/about/:id" component={About}></Route> <Route path="/my">我的</Route> <Redirect exact from="/" to="/index?city=北京"></Redirect> <Route>404</Route> </Switch> </Router> ) } }
第二例子 嵌套路由
//一级路由 import React, { Component } from 'react' import {BrowserRouter as Router,Route,Link,useLocation,Redirect,Switch} from 'react-router-dom' import Order from './Order' export default class index extends Component { render() { return ( <Router> <Link to="/order">订单</Link> <Route path="/order" component={Order}></Route> </Router> ) } } //二级路由 Order.js import React, { Component } from 'react' import {Link,Route,Switch} from 'react-router-dom' export default class Movie extends Component { render() { return ( <div> <Link to="/order/pay">已支付</Link> <Link to="/order/unpay">未支付</Link> <hr/> <Switch> <Route exact path="/order/pay">pay</Route> <Route exact path="/order/unpay">unpay</Route> </Switch> </div> ) } }
第三个例子 render
用render函数渲染组件
可以根据逻辑,来判断渲染哪一个组件
import React, { Component } from 'react' import {BrowserRouter as Router,Route,Switch,Link} from 'react-router-dom' function Login(props) { return <h2>Login</h2>; } function Pos(props) { return <h2>Pos</h2>; } export default class index extends Component { constructor(){ super() this.state = {isLogin:false} } render() { return ( <Router> <Link to="/index">首页</Link> <Link to="pos">职位管理</Link> <Route path="/index">index</Route> <Route path="/pos" render={()=>{ return this.state.isLogin ?<Pos></Pos> : <Login></Login> }}></Route> </Router> ) } }
第四例子 NavLink
- 一般用NavLink比较多
- 点中哪个链接,自动给加个 class="active"
- Link 标签不会自动加的
import React, { Component } from 'react' import {BrowserRouter as Router,Route,NavLink,useLocation,Redirect,Switch} from 'react-router-dom' export default class index extends Component { render() { return ( <Router> <NavLink to="/index">首页</NavLink> <NavLink to="/about">关于</NavLink> <hr/> <Route exact path="/index/" >index</Route> <Route path="/about/" >about</Route> </Router> ) } }
第五个例子 把a链接换成其他标签
- NavLink /Link 生成的是a链接,在外边在用li标签包裹一下怎么做呢
import React, { Component } from 'react' import {BrowserRouter as Router,Route,NavLink} from 'react-router-dom' //自定义链接 const CustomLink = (props)=>{ return <li> <NavLink to={props.path}>{props.name}</NavLink> </li> } export default class index extends Component { render() { return ( <Router> <CustomLink path="/index" name="首页"></CustomLink> <NavLink to="/movie">电影</NavLink> <hr></hr> <Route path="/index">home</Route> <Route path="/movie">movie</Route> </Router> ) } }
第六个例子 编程式导航
- 不像vue那么简单,一个函数搞定
- 这个是自己实现的
import React, { Component } from 'react' import './style.css' //这个就是定义激活active的样式 import {BrowserRouter as Router,Route,Link,NavLink,Redirect,Switch} from 'react-router-dom' import City from './City' //这个就是自己定义的编程式导航 export default class index extends Component { render() { return ( <Router> <NavLink exact to="/">我的</NavLink> <City path="/city"></City> <hr></hr> <Route exact path="/">profile</Route> <Route path="/city">City</Route> </Router> ) } } //自己定义的编程式导航 City.js import React, { Component } from 'react' import {withRouter} from 'react-router-dom' class City extends Component { handleClick=()=>{ //编程式导航 this.props.history.push(this.props.path) } render() { return ( <li className={this.props.path == this.props.location.pathname? 'active':''} onClick={this.handleClick}>城市</li> ) } } //自定义链接是拿不到路由信息, 这个必须用withRouter这个高阶函数增强一下,才能拿到路由相关的信息 export default withRouter(City)
第七个例子 用children函数来渲染组件
不管路由匹配上或者匹配不上,总会执行它
只不过区别是匹配上 props.match 不为空,否则为空
import React, { Component } from 'react' import './style.css' import {BrowserRouter as Router,Route,NavLink} from 'react-router-dom' export default class index extends Component { render() { return ( <Router> <NavLink exact to="/">我的</NavLink> <Route path="/setting" children={(props)=>{ return <NavLink to="/setting">设置</NavLink> }}></Route> <hr></hr> <Route exact path="/">profile</Route> <Route path="/setting">setting</Route> </Router> ) } }
安装 React Developer Tools 调试工具
React Developer Tools - Chrome 扩展下载安装地址
总结
理解React中虚拟DOM的概念
理解React中三种Diff算法的概念
使用JS中createElement的方式创建虚拟DOM
使用ReactDOM.render方法
使用JSX语法并理解其本质
掌握创建组件的两种方式
理解有状态组件和无状态组件的本质区别
理解props和state的区别