一:组件组合
1.1 含义
组件当中也是可以嵌套其它元素或者组件的。
1.2 实现方式:
1.在父组件中嵌套其他组件 正常写内容
2.在子组件中通过 props.children 获取组件内部嵌套的内容
1.3 代码实现
父组件:Compose.jsx:
//父组Compose.jsx件要嵌套Child.jsx组件
import React, { Component } from 'react'
import Child from './Child'
export default class Compose extends Component {
render() {
const num = 100
return (
<div>
<Child>
<h3>我是标题</h3>
<div>
<p>我是文本{ num }</p>
</div>
</Child>
</div>
)
}
}
子组件 通过 props.children 获取组件内部嵌套的内容
子组件:Child.jsx:
import React, { Component } from 'react'
export default class Child extends Component {
render() {
return (
<div>
Child
<h3>{ this.props.children }</h3>
</div>
)
}
}
二:组件状态管理
2.1 含义
State 与 props 类似,但是 state 是组件私有的,并且完全受控于所属的组件。
使用 State的目的 :管理组件内部状态,并且当组件内部状态发生变化的时候,重新渲染页面
2.2 定义数据的方式
2.2.1 constructor里定义数据
特点:
(1)数据是响应式(当数据改变,视图跟着改变)
(2)使用: this.state.变量名
2.2.2 直接定义数据
特点:
(1)定义的数据不是响应式
(2)使用:this.变量名
2.2.3 区别:
定义在constructor中的数据 ,通过指定的方法(this.setState({})修改state数据 ,同步视图
直接定义的数据,修改state数据,视图不变 this.setState({ })
//引入类组件的快捷键:rcc
import React, { Component } from 'react'
export default class Data extends Component {
constructor() {
super();
//第一种在constructor中定义状态
this.state = {
msg: 'hi,React',
count: 10,
obj: {
name: '张艺兴',
age: 30
}
}
}
// 第二种直接定义数据
num = 100;
render() {
return (
<div>
<h2>React 状态</h2>
{/* 使用constructor中定义的数据 */}
<p>msg:{this.state.msg}</p>
{/* 使用直接定义的数据 */}
<p>num:{this.num}</p>
</div>
)
}
}
2.3 修改state数据的方式
小程序如何修改状态
setData({ ..... })
React中:修改state数据需要使用 setState({ })
this.setState({
count:....
})
2.3.1 setState(obj, callback)基本用法
- setState 的第一个参数: 当 obj 为一个对象的时候,则为即将合并的 state,如何 obj 是一个函数,那么当前组件的 state 和 props 将作为参数,返回值用于合并新的 state。
- setState 的第二个参数:当 callback 为一个函数,函数执行上下文中可以获取当前 setState 更新后的最新 state 的值,可以作为以来 state 变化的副作用函数,可以 用来做一些基本的 DOM 操作等。
- 调用setState,组件的render方法会被重新调用,从而重新渲染页面
- 调用setState,子组件也会被重新渲染
2.3.2 用法示例
// 第一个参数为function类型的时候
this.setState((state, props) => {
return { number: 1 }
})
// 第一个参数为object类型的时候
this.setState(
{ number: 1 },
() => {
console.log(this.state.number) // 获取最新的number
}
)
【说明】 事件中this指向当前组件的方式
- 普通函数: this是undefined
- 箭头函数: this指向当前的组件实例对象,可以访问组件中数据 和方法
那this指向了undefined
那如何从视图中的this传到事件中?
解决方法一:使用 bind 指向当前组件,让普通函数有 this
...
clickHandler(){
// 改变状态
this.setState({
num:100
})
}
...
render(){
return(
<div>
//使用bind指向当前组件,让普通函数有this
<button onClick={ this.clickHandler.bind(this) }>改变状态</button>
</div>
)
}
...
解决方法二: 绑定 this
export default class StateDemo extends Component {
constructor(){
super()
// 定义状态
this.state = {
num:10
}
// 绑定this
this.clickHandler2 = this.clickHandler2.bind(this)
}
clickHandler2(){
this.setState({
num:1000
})
}
render() {
return (
<div>
{/* 读取状态 */}
<p>{ this.state.num }</p>
<button onClick={ this.clickHandler2 }>改变状态2</button>
</div>
)
}
}
解决方法三:使用箭头函数
import React, { Component } from 'react'
export default class StateDemo extends Component {
constructor(){
super()
// 定义状态
this.state = {
num:10
}
}
clickHandler3 = () =>{
this.setState({
num:10000
})
}
render() {
return (
<div>
<h3>State状态</h3>
{/* 读取状态 */}
<p>{ this.state.num }</p>
<button onClick={ this.clickHandler3 }>改变状态3</button>
</div>
)
}
}
2.4 State 与 Props 的区别
State:负责组件内部的状态管理
Props:负责组件与组件之间的数据交互
注意:
1.props传递的数据可以是state数据
import React, { Component } from 'react'
import SAPChild from './SAPChild'
import SAPChild2 from './SAPChild2'
export default class StateAndProps extends Component {
constructor(){
super()
this.state = {
title:"我是状态标题"
}
}
clickHandler = () =>{
this.setState({
title:"我是改变了的标题"
})
}
render() {
return (
<div>
<h3>StateAndProps</h3>
<p>{ this.state.title }</p>
<SAPChild title={ this.state.title }/>
<SAPChild title="哈哈哈"/>
<SAPChild2 />
//props传递的数据可以是state数据
<button onClick={ this.clickHandler }>修改状态</button>
</div>
)
}
}
2.setState重新渲染组件和子组件
每一次父组件的状态发生改变,并且没有做特殊的处理,无论子组件有无变化,(子组件)都会重新渲染
3.基于props初始化state
若希望传过来的属性作为子组件的状态值,需要依赖构造函数中的super ,且需要传递props进来
如下:
PropsAndState3父组件
import React, { Component } from 'react';
import PropsAndState4 from './PropsAndState4';
export default class PropsAndState3 extends Component {
render() {
return (
<div>
<h3>PropsAndState3</h3>
<PropsAndState4 title={'我是标题'} />
</div>
);
}
}
PropsAndState4 子组件
import React, { Component } from 'react';
export default class PropsAndState4 extends Component {
constructor(props) {
super(props);
this.state = {
title: props.title,
};
}
render() {
return (
<div>
<h3>PropsAndState4</h3>
<p>{this.state.title}</p>
</div>
);
}
}
三:生命周期函数
生命周期图例:React lifecycle methods diagram
3.1 生命周期函数
生命周期函数(7个,废掉了3个还剩4个)
- componentWillMount :组件将要被渲染,还没渲染,没东西(17废掉,但可以使用)
- componentDidMount : 组件渲染完成(组件进入运行状态) 【重点:网络请求,绑定事件】
- shouldComponentUpdate: 是否允许组件更新(这个函数中必须有返回值,如果返回true,表示允许更新;如果返回false,表示不允许更新;默认允许 ) 【问询函数】
- componentWillUpdate : 组件将要开始更新(17废掉,但可以使用)
- componentDidUpdate : 组件更新完成(重新进入运行状态)
- componentWillReceiveProps: 组件接收props更新(17废掉,但可以使用)
- componentWillUnmount: 组件将要卸载 ,还可以访问this, this.setState()只执行一次 【重点:收尾工作, 清空定时器】
3.2 组件的挂载
组件在首次创建后,进行第一次的渲染为挂载期。挂载期有的一些方法会被依次触发,列举如下:
- constructor :构造函数,组件被初始化时调用,在生命周期中只会被调用一次。
- static getDerivedStateFromProps :是组件类的静态函数, 组件 props 或者 state 初始化或者变化时调用,最终可以返回一个新的state对象,使用率低。
- render :定义并返回组件的界面(react元素)。
- componentDidMount : 生成了真实DOM并被渲染到页面之后调用,在生命周期中只会被调用一次
3.3 组件的更新
组件更新,指的是在组件初次渲染后,进行了组件状态的改变。React在生命周期中的更新过程包括以下几个方法:
- static getDerivedStateFromProps :组件 props 或者 state 变化时调用
- shouldComponentUpdate : 返回布尔值,true则组件会重新渲染,false则不会重新渲染。
- render :定义并返回组件的界面(react元素)。
- getSnapshotBeforeUpdate :在更新DOM之前调用,必须要返回一个值,要与
- componentDidUpdate() 方法一起使用。
- componentDidUpdate : 页面更新之后调用
3.4 组件的卸载
生命周期的最后一个过程为组件卸载期,也称为组件销毁期。该过程主要涉及一个 方法
- componentWillUnmount :组件卸载之前调用。我们可以在这个钩子函数里面执行必要的清理操作。
例如,清除定时器,关闭持续的网络连接,移除监听器等。
四:表单
4.1 受控组件
思考:vue如何双向数据绑定?
....
当页面有多个输入框的时候,为每个输入框都添加一个独立的事件处理函数太过麻烦
我们可以给每个元素添加 name 属性,并让处理函数根据 event.target.name 的值选择要执行的操作
受到状态管理控制的组件称作受控组件
import React, { Component } from 'react';
export default class FromDemo01 extends Component {
constructor() {
super();
this.state = {
username: '',
};
}
onChangeHandler = (e) => {
this.setState({
username: e.target.value,
});
// console.log(e.target.value);
};
render() {
return (
<div>
<h3>受控组件</h3>
<form>
{/* 通过value的方式绑定username -通过状态管理*/}
<input
type="text"
value={this.state.username}
onChange={this.onChangeHandler}
/>
<p>{this.state.username}</p>
</form>
</div>
);
}
}
4.2 受控组件处理多个输入
import React, { Component } from 'react';
export default class FormDemo03 extends Component {
constructor() {
super();
this.state = {
username: '',
password: '',
};
}
onChangeHandler = (e) => {
this.setState({
//Es6对象的扩展:ES6 允许字面量定义对象时,用表达式法作为对象的属性名,即把表达式放在方括号内。
// 实现动态的key
[e.target.name]: e.target.value,
});
};
loginHandler = (e) => {
//阻止默认跳转事件
e.preventDefault();
console.log(this.state);
};
render() {
return (
<div>
<h3>受控组件处理多个输入</h3>
<form action="">
<input
type="text"
name="username"
value={this.state.username}
onChange={this.onChangeHandler}
/>
<br />
<input
type="password"
name="password"
value={this.state.password}
onChange={this.onChangeHandler}
/>
{/* <input type="text" value={this.state.password} /> */}
<button onClick={this.loginHandler}>登录</button>
</form>
</div>
);
}
}
4.3 非受控组件
在一个受控组件中,表单数据是由 React 组件来管理的,为每一个受控组件添加事件处理函数,这并不完美。
可以使用替代方案:使用非受控组件,这时表单数据将由 DOM 节点来处理,可以使用ref来从DOM节点中获取表单数据
ref功能:
① 获取DOM元素
② 获取子组件实例
4.3.1. 创建步骤
① 在构造函数中创建ref语法:this.myRef = React.createRef()
② 给元素绑定ref属性
③ 获DOM元素:this.myRef.current
4.3.2.使用
import React, { Component } from 'react';
export default class FormDemo02 extends Component {
constructor() {
super();
//1.在构造函数中创建Ref语法
this.username = React.createRef();
}
loginHandler = (e) => {
e.preventDefault();
//3.获取子组件
console.log(this.username.current.value);
};
render() {
return (
<div>
<h3>非受控组件</h3>
<form action="">
{/* 2.给子组件挂载ref属性 */}
<input type="text" ref={this.username} />
<button onClick={this.loginHandler}>登录</button>
</form>
</div>
);
}
}
五:Context跨组件传递数据
5.1介绍
跨层级传值,数据深层传递
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
在一个典型的React应用中,数据是通过props属性自上而下(由父及子)进行传递的,但这种做法对于某些类型的属性而言是及其繁琐的
(例如:地区偏好,UI主题),这些属性是应用程序中许多组件都需要的。
Context提供了一种在组件之间共享此类值的方式,而不必通过组件树的逐层传递props。
5.2 使用规范
5.2.1 创建一个context对象
//MyContext.js
// 引入react
import React from "react";
export default React.createContext({ message: '默认值', age: 20 })
5.2.2 提供数据
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
即Provider属性,整体作为标签使用,这个标签上提供的一个属性value,作用是提供数据
ComponentA.jsx组件
//导入
import MyContext from './MyContext.js';
<MyContext.Provider value={{ message: this.state.message, age: this.state.age }} >
{/*被包含的组件以及包含组件的后代都可以使用value数据*/}
{/*子组件*/}
<ComponentB />
</MyContext.Provider>
5.2.3 使用数据
如果我们要在组件树中去共享某些数据,并且要避免通过中间元素传递 props,则可以使用Context来实现。
方式一:子组件名称.contextType 属性形式
设置类的contentType属性,从而获取context对象
ComponentC.jsx组件:
//子组件名称.contextType = Mycontext;
//组件内使用:
//ComponentC.jsx
import React, { Component } from 'react'
import MyContext from './MyContext'实时效果反馈
export default class ComponentC extends Component {
constructor() {
super();
this.state = {
message: '我是a的信息',
age: 20,
};
}
render() {
return (
<div>
<h3>ComponentC</h3>
{/* 获取A里的value */}
<p>{this.context.message}</p> //我是a的信息
<p>{this.context.age}</p> //20
</div>
);
}
}
//设置类的contentType属性,从而获取context对象
ComponentC.contextType = MyContext;
方式二:Context.Consumer使用数据 标签方式
作用:使用Provider上提供的数据
使用:也是作为标签使用
语法:
<MyContext.Consumer>
{value => /* 基于 context 值进行渲染*/}
</MyContext.Consumer>
ComponentD.jsx:
import React from 'react';
import myContext from '../utils/myContext';
export default function ComponentD() {
console.log(this); //undefined
return (
<div>
<h3>componentD</h3>
{/* 在函数式组件中获取context数据 */}
<myContext.Consumer>
{
value => (
<div>
<p>消息:{value.message}</p>
<p>年龄:{value.age}</p>
</div>
)
}
</myContext.Consumer>
</div>
);
}
预览
注意:
- 每个组件实例本身就有context属性,默认为空
- 需要当前子组件引入,并赋值
Class.contextType 和 Context.Consumer 的区别
- Class.contextType使用时会自动在组件上添加一个属性 context,存储离当前组件最近的Provider提供的数据,不能写多个
- Context.Consumer 可以写多个,能获取到所有Provider提供数据
5.3 Context更新
当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。
提示:
消费组件是否重新渲染不受shouldComponentUpdate的控制。
其父组件使用shouldComponentUpdate停止了渲染或者消费组件本身使用
shouldComponentUpdate停止渲染,也不影响消费组件的继续更新。
在ComponenC.jsx:中设置一个shouldComponentUpdate可以发现 并不不能影响消费组件ComponentD.jsx的继续更新
import React, { Component } from 'react';
import MyContext from '../utils/myContext';
export default class ComponentC extends Component {
constructor() {
super();
this.state = {
num: 10,
};
}
componentDidMount() {
this.setState({
num: 1000,
});
}
// 阻止生自动渲染-阻止不了 说明context可以强制ui刷新,优先级很强
shouldComponentUpdate() {
return false;
}
render() {
return (
<div>
<h3>ComponentC</h3>
{/* A里的value */}
<p>{this.context.message}</p>
<p>{this.context.age}</p>
<p>{this.state.num}</p>
</div>
);
}
}
// 通过设置contextType属性 来获取context对象
ComponentC.contextType = MyContext;
ComponentD.jsx:
import React from 'react';
import myContext from '../utils/myContext';
export default function ComponentD() {
console.log(this); //undefined
return (
<div>
<h3>componentD</h3>
{/* 在函数式组件中获取context数据 */}
<myContext.Consumer>
{(value) => (
<div>
<p>消息:{value.message}</p>
<p>年龄:{value.age}</p>
</div>
)}
</myContext.Consumer>
</div>
);
}
预览:
点击前:
点击后:
六:Fragments
七:错误边界
八:Refs & DOM
8.1 获取dom的引用
8.1.2 使用步骤
(1)创建 Refs
使用 React.createRef() 创建的。
通常将 Refs 分配给实例属性,以便可以在整个组件中引用它们。
在构造函数中创建ref语法:this.myRef = React.createRef()
(2)绑定Refs
给对应的React 元素设置 ref 属性,则相当于使用 ref 去存储 DOM 节点的引用。
{ /* 给元素绑定ref属性 */ }
(3)访问Refs
当 ref 被传递给 render 中的元素时,对该节点的引用可以在 ref 的 current 属性中被访问
操作dom 访问ref的current属性 this.自定义ref事件.current.要操作的属性
8.1.3 代码实现:
(1)创建 Refs
constructor(){
super()
//this.自定义ref = React.createRef();
this.changeHello = React.createRef();
}
(2)使用Refs
render(){
return(
<div>
{/* 把hello变成helloword 直接操作dom元素 */}
{/* 使用ref 三步骤*/}
{/* 给元素绑定ref属性 <E ref={this.myRef}></E> */}
<p ref={ this.changeHello }>hello</p>
</div>
)
}
(3)访问Refs,操作dom
import React, { Component } from 'react';
export default class Refs01 extends Component {
constructor() {
super();
// 1.在构造函数中创建ref语法:this.myRef = React.createRef()
this.changeHello = React.createRef();
}
changeDom = () => {
// 3.获取子组件并操作dom
console.log('dom元素是:', this.changeHello.current.innerHTML);
this.changeHello.current.innerHTML = '已修改';
};
render() {
return (
<div>
<h1>refs获取子元素</h1>
{/* 2.给元素绑定ref属性 <E ref={this.myRef}></E> */}
<p ref={this.changeHello}>hello</p>
{/* 把hello变成helloword 直接操作dom元素 */}
{/* 使用ref 三步骤*/}
<button onClick={this.changeDom}>获DOM元素并修改</button>
</div>
);
}
}
提示:
React 会在组件挂载时给 current 属性传入 DOM 元素,并在组件卸载时传入 null 值。
ref会在 componentDidMount 或 componentDidUpdate 生命周期钩子触发前更新
8.2 获取组件类实例
使用Refs的时候,当 ref 属性用于自定义 class 组件时, ref 对象接收组件的实例作为其 current 属性。
8.2.1步骤和实现
① 在子组件中定义方法和属性:
//在子组件中定义方法和属性:
import React, { Component } from 'react';
export default class RefsCom02 extends Component {
constructor() {
super();
this.state = {
num: 100,
};
}
//在子组件中定义方法
getInfo() {
console.log('我是子组件的信息内容');
}
render() {
return (
<div>
<h2>子组件内定义组件的实例方法和属性</h2>
{/* 定义属性 */}
<p>子组件的属性:{this.state.num}</p>
</div>
);
}
}
② 在父组件中使用refs来强行使用子组件的方法和属性
import React, { Component } from 'react';
import RefsCom02 from './RefsCom02';
export default class Refs01 extends Component {
constructor() {
super();
// 在构造函数中创建ref语法:this.myRef = React.createRef()
this.getChild = React.createRef();
}
// 在生命周期函数中读取子组件的实例方法和属性
componentDidMount() {
this.getChild.current.getInfo(); //使用子组件的方法 --我是子组件的信息内容
console.log(this.getChild.current.state.num); //查看子组件的属性 --100
}
render() {
return (
<div>
<h2>父组件:refs获取组件类实例方法和属性</h2>
<RefsCom02 ref={this.getChild} />
</div>
);
}
}
8.3 转发 refs 到 DOM 组件
8.4 回调 Refs
九:高阶组件
9.1 含义
高阶组件(HOC:Higher-Order Components)是一个函数
高阶组件(HOC) 接受一个组件为参数,返回值为新组件
可以把 高阶组件 看作是 组件的加工厂 ,接收旧组件返回包装后的新组件
9.2 什么情况下使用高阶组件
react 如果有多个组件都用到了 同一段逻辑 , 这时,就可以把共同的逻辑部分提取出来,利用高阶组件的形式
将这段逻辑整合到每一个组件中, 从而减少代码的逻辑重复
9.3 案例
在本地存储中存储一个字段和对应的值
存储成功后:
假设一个使用高阶组件的场景:多个组件都要读到这个key和value
普通的写法是每一个组件都需要写相同读取数据的逻辑
import React, { Component } from 'react';
export default class HOCdemo1 extends Component {
constructor() {
super();
this.state = {
info: '',
};
}
componentDidMount() {
this.setState({
//把设置的value值赋值给info
info: localStorage.getItem('key'),
});
}
render() {
return (
<div>
<h3>HOCdemo01</h3>
<p>{this.state.info}</p> {/*hello*/}
</div>
);
}
}
把相同的逻辑提取出来作为高阶组件
SameInfo.jsx:
import React from 'react';
// sameInfo 函数名
//ComponentInfo 旧组件
// return 新组件
export default function sameInfo(ComponentInfo) {
return class extends React.Component {
// 提取共同的逻辑
constructor() {
super();
this.state = {
info: '',
};
}
componentDidMount() {
this.setState({
info: localStorage.getItem('key'),
});
}
// 渲染成一个新的组件
render() {
// 返回一个旧组件
// 组件的最终目的是读到info 因此info作为参数返回
// ...this.props 传递给当前组件的属性继续向下传递
return <ComponentInfo info={this.state.info} {...this.props} />;
}
};
}
使用高阶组件:-创建新组建-使用相同的逻辑
UseDemo01.jsx:
import React, { Component } from 'react';
// 引入高阶组件
import sameInfo from './SameInfo';
class UseDemo01 extends Component {
render() {
return (
<div>
<h2>使用高阶组件1</h2>
<p>{this.props.info}</p>
</div>
);
}
}
export default sameInfo(UseDemo01);
预览:
9.4 高阶组件(HOC)组合参数组件
我们来思考一个问题,使用高阶组件是否可以修改一个不满足当前效果的组件呢?
答:通过组件的原型,重绘了整个render函数
高阶组件withHigh.jsx:
import React from 'react';
export default function withHigh(ComponentNum) {
// 修改传入的组件的render函数
ComponentNum.prototype.render = function () {
return <div>使用高阶组件修改后的界面</div>;
};
//返回修改后的组件
return ComponentNum;
}
预览:
原先:
使用高阶组件强行更改后的界面: