前言
React 全家桶:React-Router 路由库、PubSub 消息管理库、Redux 集中状态管理库、Ant-Design UI
一、React是什么?
React 是 Facebook 开发的,用于构建用户界面的开源 JavaScript 库(是一个将数据渲染为HTML视图的开源 JavaScript 库)
页面渲染步骤:
- 1、发送请求获取数据
- 2、处理数据(过滤、整理格式等)
- 3、操作 DOM 呈现页面 (React 的工作)
原生 JavaScript 与 React 比较:
- 原生JavaScript
操作 DOM 繁琐、效率低
直接操作 DOM 、进行大量的重绘重排
没有组件化编码、复用率低
命令式编码 - React
组件化模式、声明式编码、提高开发效率及组件复用率
React Native 中可以使用 React 语法进行移动端开发
高效:虚拟 DOM(不总直接操作DOM) + 优秀 Diffing 算法(最小化页面重绘)尽量减少与真实 DOM 的交互
二、基础内容
1. React 相关 js 库
- react.js:React 核心库
- react-dom.js: 提供操作 DOM 的 React 扩展库
- babel.min.js:解析 JSX 语法代码转化为 JS 代码的库(es6 ⇒ es5、import 模块化、JSX ⇒ JS)
2. React 开发者调试工具
React Developer Tools
3. JSX语法规则
- 1、定义虚拟DOM时,不要写引号
- 2、标签中混入 JS 表达式时要用 {}
一定要注意区分:【JS表达式】 与 【JS语句(代码)】
JS 表达式: 一个表达式会产生一个值,可以放在任何一个需要值的地方:a、a+b、demo(1)、arr.map、function demo(){}
JS 代码: 控制代码走向,没有返回值:if(){}、 for(){}、switch(){case:xx}
- 3、样式的类名指定不要用 class,要用 className
- 4、内联样式,要用 style ={ {key: value}} 的形式去写
- 5、虚拟 DOM 只能有一个根标签
- 6、标签必须闭合
- 7、标签首字母
若小写字母开头,则将该标签转为 html 中同名元素,若html中无该标签对应的同名元素,则报错
若大写字母开头,react 渲染对应的组件,若组件没有定义,则报错
<div id="root"></div>
<script type="text/babel">
const catName = '花姐'
// 用 JSX 创建虚拟 DOM
const VDOM = (
<div>
<h2 className="name">
<span style={
{
color: "orange", fontSize: "20px"}}>{
catName}</span>
</h2>
</div>
)
ReactDOM.render(VDOM, document.getElementById('root'))
</script>
4. 模块与组件、模块化与组件化
-
模块(JS模块)
理解:向外提供特定功能的 js 程序,一般就是一个 js 文件
作用:复用 js ,简化 js 的编写,提高 js 的运行效率 -
组件
理解:用来实现局部功能效果的代码和资源的集合(html、css、js、image等)
作用:复用编码、简化项目编码、提高运行效率 -
模块化:当应用的 js 都以模块来编写的,这个应用就是一个 模块化 的应用
-
组件化:当应用都是以组件的方式实现,这个应用就是一个 组件化 的应用
5. 类的基本知识
// 创建一个 cat 类
class Cat{
// 构造器方法
constructor(name){
// 构造器中的 this 指向类的实例对象: 谁调用了new, this 指向谁
this.name = name
}
// 一般方法:放在了类的原型对象(_proto_)上, 供实例使用
// 通过 Cat 实例调用一般方法时,this 指向 Cat 实例
sayName(){
console.log(`我叫${
this.name}`)
}
}
// 创建一个 cat 的实例对象
const HJ = new Cat('花姐')
// 创建一个 Minicat 类,继承于 Cat 类
class Minicat extends Cat {
constructor(name, age){
// super 作用:调用父类的构造器函数;super 必须写在其他参数之前
super(name);
this.age = age
}
}
// 创建一个 Minicat 的实例对象
const mini = new Minicat('橘宝', 2)
/*
总结:
1、类中的构造器不是必须写的,要对实例进行一些初始化操作的时候,如添加属性才写;
2、如果 A 类继承了 B 类,且 A 类写了构造器,那 A 类中构造器的 super 是必须调用的;
3、类中的方法,都是放在类的原型对象上(_proto_),供实例使用
*/
三、React 面向组件编程
1. 函数式组件
适用于 简单组件(无state) 的定义
// 创建函数式组件
function Demo(){
// babel 编译后开启了严格模式,严格模式禁止自定义的函数 this 指向 widow
console.log(this); // undefined
return <h2>我是函数式组件</h2>
}
// 渲染组件到页面
ReactDOM.render(<Demo/>, document.getElementById('root'));
/*
执行了 ReactDOM.render((<Demo />.... 之后:
1、React 解析组件标签,找到了 Demo 组件;
2、发现是函数式组件的,随后调用该函数,将返回的虚拟 DOM 转为真实 DOM ,随后呈现在页面中
*/
2. 类式组件
适用于 复杂组件(有state) 的定义
/*
创建类式组件
1、React.Component:React 内置类
2、内部 render(){} 必须写,放在 Demo 原型对象上,供实例使用
*/
class Demo extends React.Component {
render(){
// render 中的 this 指向 Demo 组件实例对象
console.log(this);
return <h2>我是类式组件</h2>
}
}
// 渲染组件到页面
ReactDOM.render(<Demo />, document.getElementById('root'))
/*
执行了 ReactDOM.render((<Demo />.... 之后:
1、React 解析组件标签,找到 Demo 组件;
2、发现组件是使用类定义的,随后 new 出来该类的实例,并通过该实例调用到原型上的 render 方法
3、将 render 返回的虚拟 DOM 转化为真实的 DOM, 随后呈现在页面中
*/
3. 组件实例的三个核心属性: state、refs、props
- state
// state 复杂写法
class Weather extends React.Component {
constructor(props){
super(props);
this.state = {
isHot: false };
// .bind(this)用来解决 this 指向问题
this.changeWeather = this.changeWeather.bind(this);
}
/*
changeWeather 放在 Weather 原型对象上,供实例使用
由于 changeWeather 是作为 onClick 回调,而不是通过实例调用的,是直接调用
类中的方法默认开启了举报的严格模式,所以方法中的 this 为 undefined
*/
changeWeather(){
this.setState({
isHot: !this.state.isHot}) }
render(){
const {
isHot } = this.state;
return <p onClick={
this.changeWeather} >今天天气{
isHot}</p>
}
}
// state 简写方式: 省略类构造器,函数修改 this 指向
class Weather extends React.Component {
// 类中可以直接写赋值语句,给类添加属性(不能写var\let\const)
state = {
isHot: fasle };
/*
赋值语句的形式 + 箭头函数,修改函数 this 指向
箭头函数没有自己的this, 声明时会向外寻找 this,始终指向函数声明时所在的作用域下的 this 的值
*/
changeWeather = () => this.setState({
isHot: !this.state.isHot})
render(){
const {
isHot } = this.state;
return <p onClick={
this.changeWeather} >今天天气{
isHot}</p>
}
}
/*
总结:
1、组件中 render 方法中的 this 为组件实例对象
2、组件自定义的方法中 this 为undefined, 解决方法:
通过函数对象的 bind(), 强制绑定this
箭头函数 + 赋值语句,声明方法
3、状态数据,通过 setState 修改
this.state.isHot = true 修改 isHot 的值会成功,但是不会触发渲染
需要通过 setState 修改才会触发重新渲染
4、标签绑定事件 onClick 要大写
*/
- props
理解:每个组件都有一个 props(properties) 属性,组件标签的所有属性都保存在 props 中
作用:通过标签属性从组件外部向组件内部传递变化的数据
注意:组件内部不要修改 props 数据,会报错
let obj ={
name: '花姐'}
// 1、函数组件使用 props,参数形式
function Demofn(props){
const {
name} = props
return(
<p>{
name}</p>
)
}
// 对标签属性进行类型、必要性限制
Demofn.propTypes = {
name: PropTypes.string.isRequired, // string 表示为字符串,isRequired 表示必传
}
// 指定默认标签属性值
Demofn.defaultProps = {
name: '喵'
}
ReactDOM.render(<Demofn {
...obj}/>, document.getElementById('root'))
// 2、类式组件中的构造器 与 props
class Democlass extends React.Component {
// 构造器是都接收 props,是否传递给 super 取决于是否希望在构造器中通过 this 访问 props
// 开发过程中一般不使用构造器
constructor(props){
super(props);
console.log(this.props); // 需要调用super(props),才能在构造器中使用 this.props,否则为 undefined
}
// 对标签属性进行类型、必要性限制
static propTypes = {
name: PropTypes.string.isRequired, // string 表示为字符串,isRequired 表示必传
}
// 指定默认标签属性值
static defaultProps = {
name: '喵'
}
render(){
let {
name} = this.props
return(
<p>{
name}</p>
)
}
}
// {...obj} 扩展运算符批量传递
ReactDOM.render(<Democlass {
...obj}/>, document.getElementById('root2'))
- refs (注意 ref 与 refs)
- 字符串形式 (不推荐使用,存在效率问题)
class Demo extends React.Component {
getRef = () => {
// this.refs.btn 是真实 DOM
const {
btn } = this.refs
}
render(){
return(
<button ref="btn" onClick={
this.getRef}>字符串形式 ref (不推荐使用)</button>
)
}
}
ReactDOM.render(<Demo/>, document.getElementById('root'))
- 回调函数形式(内联写法)
class Demo extends React.Component {
getRef = () => {
const {
btn } = this
}
render(){
return(
<button ref={
c=> this.btn = c} onClick={
this.getRef}>回调形式 ref, 内联写法</button>
)
}
}
ReactDOM.render(<Demo/>, document.getElementById('root'))
回调 ref 在页面更新过程中调用次数的问题(可以忽略,影响不大):
第一次传入 null,第二次传入 DOM,每次更新渲染时会创建一个新的函数实例,React 会清空就的 ref 并设置新的 ref
解决:通过 ref 回调函数定义成 class 的绑定函数可以解决
class Demo extends React.Component {
saveBtn = (c) => {
this.btn = c }
getRef = () => {
const {
btn } = this }
render(){
return(
<button ref={
this.saveBtn} onClick={
this.getRef}>回调形式 ref, 内联写法</button>
)
}
}
ReactDOM.render(<Demo/>, document.getElementById('root'))
- createRef 形式
React.createRef()调用后可以返回一个容器,该容器可以存储被 ref 所标识的节点
该容器专人专用,需要多个 ref 需要重复 React.createRef()
class Demo extends React.Component {
myRefs = React.createRef()
getRef = () => {
console.log(this.myRefs.current)
}
render(){
return(
<button ref={
this.myRefs} onClick={
this.getRef}>createRef 形式</button>
)
}
}
ReactDOM.render(<Demo/>, document.getElementById('root'))
4. React 中的事件处理
- 1、通过onXxx 属性指定事件处理函数(注意大小写)
React 使用的是自定义(合成)事件,而不是使用的原生 DOM 事件,为了更好的兼容性
React 中的事件是通过事件委托方式处理的(委托给组件最外层的元素)
事件的执行顺序为原生事件先执行,合成事件后执行
合成事件会冒泡绑定到 document 上,所以尽量避免原生事件与合成事件混用
如果原生事件阻止冒泡,可能会导致合成事件不执行,因为需要冒泡到 document 上合成事件才会执行
react 事件不能采用 return false 的方式来阻止浏览器的默认行为,而必须要地明确地调用 event.preventDefault() 来阻止默认行
- 2、通过 event.target 得到发生事件的 DOM 元素对象 – 不要过度使用 ref
发生事件的元素跟获取数据的元素是同个时,就省略 ref ,使用event.target
5. 受控组件 与 非受控组件
受控组件:页面所有输入都由 state 控制
非受控组件: 现取现用 ref
6. 高阶函数 - 函数柯理化
-
高阶函数
如果一个函数符合下面2个规范中的任何一个,那么函数就是高阶函数
1、若A函数,接收的参数是一个函数,A就可以称为高阶函数
2、若A函数,函数调用的返回值依然是一个函数,A就可以称为高阶函数
例:promise(() => {})、setTimeout(() => {})、arr.map(() => {}) (数据常见的方法)等 -
函数柯里化
通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式
function sum(a){
return b => {
return c =>{
return a + b + c
}
}
}
sum(1)(2)(3)
react 表单柯里化案例:
class Login extends React.Component {
state = {
username: '',
password: ''
}
handleSubmit = () => {
event.preventDefault();
const {
username, password} = this.state
}
// 柯里化实现
saveFormData = (type) => {
return (event) => {
this.setState({
[type]: event.target.value})
}
}
// 不使用柯里化实现
saveTypeData = (type, event) => {
this.setState({
[type]: event.target.value})
}
render(){
return(
<form onSubmit={
this.handleSubmit}>
{
/* 柯里化实现 */}
用户名:<input type="text" name="username" onChange={
this.saveFormData('username')} />
{
/* 不使用柯里化实现 */}
密码:<input type="password" name="password" onChange={
event => this.saveTypeData('password', event)} />
<button>登录</button>
</form>
)
}
}
7. 组件生命周期
- constructor:构造函数
- render:渲染
- getDerivedStateFromProps:从props得到一个派生的state(17 新增),返回 null 或 状态对象
- componentDidMount:组件挂载完毕
- shouldComponentUpdate:控制组件是否更新 返回值 true 更新, false 不更新 (this.setState() 触发)
- getSnapshotBeforeUpdate:组件在更新之前获取快照(17 新增)
- componentDidUpdate:组件更新完毕
- React.unmountComponentAtNode(document.getElementById(‘root’)): 卸载组件
- componentWillUnmount:组件将要被卸载
componentWillMount :组件将要挂载(17 废弃)
componentWillUpdate :组件将要更新(17 废弃)(this.forceUpdate() 触发)
componentWillReceiveProp s:子组件将要接收新的 props,首次不调用(17 废弃)
旧生命周期总结:
-
初始化阶段(挂载时):由 ReactDOM.render() 触发初次渲染
constructor()
componentWillMount()
render()
componentDidMount() -
更新阶段:由组件内部 this.setStat