生命周期补充(不常用):
案例:
import React, { Component } from 'react'
export default class App extends Component {
state = {
num : 100,
list: []
}
//获取到的是更新前的props 和 state
getSnapshotBeforeUpdate(prevProps,prevState){ //必须搭配componentDidUpdate 生命周期使用
console.log(prevState);
// 返回值 快照
const heigth = document.getElementById('content').clientHeight
console.log([document.getElementById('content')]);
return heigth
}
// prev 之前的旧的数据 snapshot就是 getSnapshotBeforeUpdate 的return返回值
componentDidUpdate(prevProps, prevState, snapshot){
console.log('更新前的列表高度:',snapshot);
}
render() {
return (
<div>App
<button onClick={() => this.setState({num:this.state.num +1})}> {this.state.num}</button>
<div id='content'>
{this.state.list.map((item,index) => (
<div key={index}>{item}</div>
))}
</div>
<button onClick={this.add}> 添加</button>
</div>
)
}
add = () => {
const time = new Date().getTime()
this.setState({list: [...this.state.list,time]})
}
}
一、组件
1.特别说明
有以下示例代码:
import React, { Component } from "react";
// 编辑功能的案例
class App extends Component {
state = {
msg: "hello world",
};
render() {
return (
<div>
<input type="text" value={this.state.msg} />
</div>
);
}
}
export default App;
通过运行后我们可以在浏览器的consle
控制台找到React给予我们的提示:
Warning: You provided a
value
prop to a form field without anonChange
handler. This will render a read-only field. If the field should be mutable usedefaultValue
. Otherwise, set eitheronChange
orreadOnly
.
通过上述的警告提示,我们可以得知,在React中并不存在类似于Vue的双向数据绑定操作。此处需要注意以下几点:
-
Vue中的
v-model
是语法糖 -
在React里使用的是单向数据流(vue中是双向绑定)
由于在React里数据流是单向的,所以我们就必须得考虑一个问题:怎么获取用户在表单中输入的数据呢?解决办法:
-
给表单项添加
onChange
事件,通过事件处理实现双向绑定效果【受控组件】 -
给表单项的value/checked,设置成defaultValue/defaultChecked,结合ref对象实现双向效果【非受控组件】
React推荐我们使用受控组件。
2.受控组件
将state
与表单项中的value
值绑定在一起,由state
的值来控制(onChange事件)表单元素的值,称为受控组件。
绑定步骤:
-
在state中添加一个状态,作为表单元素的value值
-
给表单元素绑定change事件,将表单元素的值设置为state的值
案例:
import React, { Component } from 'react'
// 受控组件
// 通过 state 控制组件的状态
// 1.生命一个state
// 2.将 state绑定到表单的value值, 根据 onChange 事件修改state里面的参数
// 3.最终获取表单数据 直接使用state里的状态参数即可
export default class App extends Component {
state = {
name: '',
password: '',
}
render() {
const {name, password} = this.state
return (
<div>
<input type="text" placeholder='用户名' value={name} onChange={(e)=> this.setState({name: e.target.value})}/>
<input type="password" placeholder='密码' value= { password} onChange={(e)=> this.setState({password: e.target.value})}/>
<button onClick={this.submit}>提交</button>
</div>
)
}
submit = () => {
console.log(this.state);
}
}
3.非受控组件
没有和state数据源进行关联的表单项,而是借助ref,使用元素DOM方式获取表单元素值
使用步骤
-
调用React.createRef()方法创建ref对象
-
将创建好的ref对象添加到文本框中
-
通过ref对象获取到文本框的值
提示:一般表单项少的时候可以考虑使用非受控组件。
案例:
import React, { Component, createRef } from "react";
// 非受控组件
// ref 属性绑定 绑定子组件获取到的就是组件实例 绑定的是标签获取到的就是DOM元素
// 1. 导入createRef 方法
// 2. 生成一个ref属性
// 3. 将属性绑定给标签
// 4. this.ref属性.current 获取到绑定的组件实例或者DOM对象
export default class App extends Component {
nameRef = createRef();
passwarodRef = createRef();
render() {
return (
<div>
<input type="text" placeholder="用户名" ref={this.nameRef} />
<input type="password" placeholder="密码" ref={this.passwarodRef} />
<button onClick={this.submit}>提交</button>
</div>
);
}
submit = () => {
console.log(this.nameRef);
console.log(this.passwarodRef);
// 基础语法
console.log({ name: this.nameRef.current.value, password : this.passwarodRef.current.value });
// 替代语法
const uname = this.nameRef.current.value
const uPassword = this.passwarodRef.current.value
console.log({uname,uPassword});
};
}
表单登录案例:
1.fetch登录:
/***
* 非受控组件
* ref属性绑定 绑定子组件获取到的就是组件实例 绑定的是标签获取到的就是DOM对象
* 1、导入createRef方法
* 2、生成一个ref属性
* 3、将属性绑定给标签
* 4、this.ref属性.current 获取到绑定的组件实例或者DOM对象
*
*/
import React, { Component, createRef } from 'react'
export default class App extends Component {
unameRef = createRef()
upassRef = createRef()
render() {
return (
<div>
<input type="text" placeholder="用户名" ref={this.unameRef} />
<input type="password" placeholder="密码" ref={this.upassRef} />
<button onClick={this.submit}>提交</button>
</div>
)
}
submit = () => {
// console.log(this.unameRef)
// console.log(this.upassRef)
const uname = this.unameRef.current.value
const upass = this.upassRef.current.value
const url = 'http://127.0.0.1:5000/api/v1/login'
fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: uname, password: upass })
})
.then((res) => res.json())
.then((res) => {
console.log(res)
if (res.code === 0) {
alert('登录成功')
} else {
alert('登录失败')
}
})
}
}
2.rcc组件登录流程
import React, { Component, createRef } from 'react'
import { Form, Input, Button, Toast } from 'antd-mobile'
import axios from 'axios'
export default class App extends Component {
onFinish = (values) => {
console.log(values)
const url = 'http://127.0.0.1:5000/api/v1/login'
axios.post(url, values).then((res) => {
console.log(res)
if (res.data.code === 0) {
Toast.show({
icon: 'success',
content: '登录成功'
})
} else {
Toast.show({
icon: 'fail',
content: '登录失败',
duration:1500,
afterClose: () => {
// window.location.reload()
// 通过ref属性绑定组件实例 获取到组件的实例 并使用其组件实例方法清空表单字段内容
this.formRef.current.setFieldsValue({ username: '', password: '' })
}
})
}
})
}
formRef = createRef()
render() {
return (
<div>
<Form
ref={this.formRef}
onFinish={this.onFinish}
layout="horizontal"
mode="card"
footer={
<Button block type="submit" color="primary" size="large">
提交
</Button>
}
>
<Form.Item name="username" label="用户名">
<Input placeholder="例如:admin" />
</Form.Item>
<Form.Item name="password" label="密码">
<Input placeholder="例如:admin123" type="password" />
</Form.Item>
</Form>
</div>
)
}
}
二、组件通信
1.父传子
该传值的实现可以分为两种,思想大致如下:
-
父通过
ref
标记子组件,通过ref
获取子组件实例对象,父将自己的状态或数据以实参形式传递给子组件中预设的方法,在子组件中的预设方法以形参形式接收父组件传递来的数据,并保存到子组件自身的状态 -
在父组件中定义一个获取父组件自身状态或数据的方法,将该方法以
props
属性的形式传递给子组件,子组件收到后执行该方法即可获取到父组件的状态或数据
案例:
父组件
import React, { Component ,createRef} from 'react'
import Child from './components/Child'
/**
* 父传子
* 1.props
* 2.children 这个类似于props
* 3.父组件定义一个方法 传递给子组件 子组件调用 父组件方法通过返回值传递到子组件(通过props直接调用方法)
* 4.子组件定义一个方法 父组件调用 传递参数给子组件 实现父传子
*/
export default class App extends Component {
state= {
msg: '父组件的值20'
}
childRef = createRef()
sendData = () => {
return this.state.msg
}
componentDidMount(){
this.childRef.current.getData({num:250})
}
render() {
return (
<div>
父组件
<Child ref= {this.childRef} msg={this.state.msg} sendData= {this.sendData}> {this.state.msg}</Child>
</div>
)
}
}
子组件
import React, { Component } from 'react'
export default class Child extends Component {
getData = (data) => {
console.log(data);
}
render() {
// console.log(this.props);
// console.log(this.props.sendData());
return (
<div>Child</div>
)
}
}
2.子传父
该传值的实现可以分为两种,思想大致如下:
-
(父主动获取子的数据)父通过
ref
标记子组件,随后通过子组件实例对象获取子组件的数据 -
在父组件中预埋一个修改父组件自身的方法,将该方法以
props
的形式传递给子组件,子组件收到方法时去调用,并且将自己需要给父的数据以实参的形式给这个方法
案例:
父组件
import React, { Component, createRef } from 'react'
import Child1 from './components/Child1'
/**
* 子传父
* 1.ref
* 2.父组件定义方法 子组件调用 通过参数传递的方式 实现子传父
* 3.子组件定义方法 父组件调用 通过返回值 传递参数 实现父传子
*/
export default class App extends Component {
getData = (data) => {
console.log(data);
}
componentDidMount(){
// console.log(this.Child1Ref.current.state);
console.log(this.Child1Ref.current.sendData());
}
Child1Ref= createRef()
render() {
return (
<div>
<Child1 ref={this.Child1Ref} getData={this.getData}></Child1>
</div>
)
}
}
子组件
import React, { Component } from "react";
export default class Child1 extends Component {
state = {
msg: "我是子组件",
};
componentDidMount() {
this.props.getData(this.state);
}
sendData = () => {
return this.state;
};
render() {
return <div>Child1</div>;
}
}
3.跨组件传值
在react没有类似vue中的事件总线来解决这个问题。在实际的项目中,当需要组件间跨级访问信息时,如果还使用组件层层传递props,此时代码显得不那么优雅,甚至有些冗余。在react中,我们还可以使用context来实现跨级父子组件间的通信。
import React, { Component, createContext } from "react"
const {
Provider,
Consumer
} = createContext()
提示:在React的context中,数据被看成了商品,发布数据的组件会用provider身份,接收数据的组件使用consumer身份。
-
创建Context对象
当React渲染一个订阅了这个Context对象的组件,这个组件会从组件树中离自身最近的那个匹配的Provider中读取到当前的context值。
// 定义全局context
// 由于这个操作后期可能被复用,建议独立文件去创建。此处以`src/Context/index.js`为例
import { createContext } from "react"
export default createContext()
-
发布消息
在App.jsx组件中发布消息,这样所有的组件都可以消费它的消息。
import React, { Component } from "react";
import Cmp1 from "./Components/Cmp1";
import Cmp2 from "./Components/Cmp2";
// 导入context对象
import ContextObj from "./Context/index";
let { Provider } = context;
class App extends Component {
state = {
count: 12345,
};
render() {
return (
<div>
<Provider value={this.state.count}>
<Cmp6></Cmp6>
<Cmp7></Cmp7>
</Provider>
</div>
);
}
}
export default App;
-
组件消费
在子组件中通过Api完成消费动作,从而实现消息通信。消费的方式有2种:
方式1:通过组件消费
import React, { Component } from "react";
import ContextObj from "../Context/index";
let { Consumer } = ContextObj;
class Cmp1 extends Component {
render() {
return (
<div>
<Consumer>
{(value) => {
return <div>获取到的值是:{value}</div>;
}}
</Consumer>
</div>
);
}
}
export default Cmp1;
方式2:通过绑定静成属性来消费
import React, { Component } from "react";
import ContextObj from "../Context/index";
class Cmp2 extends Component {
static contextType = ContextObj;
render() {
return <div>{this.context}</div>;
}
}
export default Cmp2;
案例:
import React, { Component, createContext } from "react";
/**
* context 跨层级传参
* 1.导入createContext
* 2.生成一个context
* 3.使用 provider 进行数据的发布
* 4.使用 Consumer 接收传递的参数 或者 static 方式 接收传递的参数()
*/
const context = createContext();
const Provider = context.Provider; // 写法二
// const Consumer = context.Consumer;
// 爷爷组件
export default class App extends Component {
state = {
money: 100,
};
render() {
return (
<div>
{/* 写法一 */}
{/* <context.Provider>
<Father money={this.state.money}></Father>
</context.Provider> */}
{/* 写法二 */}
<Provider value={this.state.money}>
<Father></Father>
</Provider>
</div>
);
}
}
// 爸爸组件
class Father extends Component {
render() {
return (
<div>
<Child></Child>
</div>
);
}
}
// 儿子或孙子
class Child extends Component {
static contextType = context;
render() {
return (
<div>
{/* 写法一 */}
{/* <Consumer>{(value) => <div>Child 爷爷给了{value}元</div>}</Consumer> */}
{/* 写法二 */}
爷爷给了{this.context}元
</div>
);
}
}
使用 Context 之前的考虑
Context 主要应用场景在于很多不同层级的组件需要访问同样一些的数据。请谨慎使用,因为这会使得组件的复用性变差。
如果你只是想避免层层传递一些属性,组件组合(component composition)有时候是一个比 context 更好的解决方案。
比如,考虑这样一个 Page
组件,它层层向下传递 user
和 avatarSize
属性,从而让深度嵌套的 Link
和 Avatar
组件可以读取到这些属性:
<Page user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<PageLayout user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<NavigationBar user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<Link href={user.permalink}>
<Avatar user={user} size={avatarSize} />
</Link>
如果在最后只有 Avatar
组件真的需要 user
和 avatarSize
,那么层层传递这两个 props 就显得非常冗余。而且一旦 Avatar
组件需要更多从来自顶层组件的 props,你还得在中间层级一个一个加上去,这将会变得非常麻烦。
一种 无需 context 的解决方案是将 Avatar 组件自身传递下去,因为中间组件无需知道 user
或者 avatarSize
等 props:
function Page(props) {
const user = props.user;
const userLink = (
<Link href={user.permalink}>
<Avatar user={user} size={props.avatarSize} />
</Link>
);
return <PageLayout userLink={userLink} />;
}
// 现在,我们有这样的组件:
<Page user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<PageLayout userLink={...} />
// ... 渲染出 ...
<NavigationBar userLink={...} />
// ... 渲染出 ...
{props.userLink}
这种变化下,只有最顶部的 Page 组件需要知道 Link
和 Avatar
组件是如何使用 user
和 avatarSize
的。
这种对组件的控制反转减少了在你的应用中要传递的 props 数量,这在很多场景下会使得你的代码更加干净,使你对根组件有更多的把控。但是,这并不适用于每一个场景:这种将逻辑提升到组件树的更高层次来处理,会使得这些高层组件变得更复杂,并且会强行将低层组件适应这样的形式,这可能不会是你想要的。
而且你的组件并不限制于接收单个子组件。你可能会传递多个子组件,甚至会为这些子组件(children)封装多个单独的“接口(slots)”,正如这里的文档所列举的
function Page(props) {
const user = props.user;
const content = <Feed user={user} />;
const topBar = (
<NavigationBar>
<Link href={user.permalink}>
<Avatar user={user} size={props.avatarSize} />
</Link>
</NavigationBar>
);
return (
<PageLayout
topBar={topBar}
content={content}
/>
);
}
这种模式足够覆盖很多场景了,在这些场景下你需要将子组件和直接关联的父组件解耦。如果子组件需要在渲染前和父组件进行一些交流,你可以进一步使用 render props。
但是,有的时候在组件树中很多不同层级的组件需要访问同样的一批数据。Context 能让你将这些数据向组件树下所有的组件进行“广播”,所有的组件都能访问到这些数据,也能访问到后续的数据更新。使用 context 的通用的场景包括管理当前的 locale,theme,或者一些缓存数据,这比替代方案要简单的多。