组件间通信
如果不借助第三方库,原生react是只支持单向流动:父级可以向子级通过props的方式发送state和回调函数,而子级不能向父级发送事件或者修改父级的props。所以如果子级想要修改父级组件的状态(也就是数据),只可以通过父级传递下来的回调函数进行执行。
相邻层级组件间的通信(同级/父子)
借助上一篇笔记中最后写到的实例,如果我们想要在展开一栏的时候,关闭掉其他的栏,可是在App组件中是直接实例化了三个Dl组件,这三个组件之间是无法互相通信的,只有在他们的父组件(也就是App)中声明一个state状态,利用props传递给每个实例,并声明一个改变该state状态的回调函数,一并传给三个实例。这样当每个Dl实例触发这个回调函数时,App组件就会修改state状态,从而实现了同级组件之间的通信
//App组件
//声明一个state状态,负责表示哪个Dl窗口是展开的。并声明一个回调函数,负责控制state状态。
state = {
openname: "" //openname 记录的是当前应该哪一项展开,如果为空则都不展开
}
changeOpen = (name) => { //回调函数触发时,会讲修改state状态
this.setState({
openname: name
})
}
render() {
const {openname} = this.state;
return <div className="friend-list">
{Object.keys(data).map((item,index)=>{
return <Dl
key={index}
data={data[item]}
name={item}
openname={openname} //把这个状态传递给所以实例化的Dl组件
changeOpen = {this.changeOpen} //把更改openname状态的回调函数也传递给所有Dl组件
/>
})}
</div>
}
// Dl组件
render() {
let { data, name, openname, changeOpen } = this.props; // 从props中拿到openname和changeOpen
return <dl className={`friend-group ${openname === name? "expanded" : ""}`}> <!--通过openname判断是否展开-->
<dt onClick={() => {
changeOpen(openname === name?"":name); //通过拿到的changeOpen回调函数,修改openname状态
}}>{data.title}</dt>
{data.list.map((item, index) => {
return <dd key={index}>{item.name}</dd>
})}
</dl>
}
跨级组件间通信(爷孙)
假设有这样三个组件:
App
组件:是根组件Child
组件:是App组件的子组件SubChild
组件:是Child组件的子组件
这样如果SubChild
组件接收来自App
组件的信息,那么中间必须要过一道Child
,如果用笨方法,可以通过一层层的拿到props
=>结构props
=>传递给下一层
=>下一层继续结构props
,这样慢慢的传递下去。显然这样可以,但是很麻烦,所以原生React
提供了一种跨级组件通信的方式(有点类似vuex
)—— context
首先需要通过createContext()
方法声明一个context实例,这里为了方便区分,就叫它contextStore
,contextStore
实例提供了两个方法Provider
和Consumer
,从名字就可以看出来是分别表示提供和接收状态的。
import {createContext} from "react";
const contextStore = createContext();
const {Provider,Consumer} = context;
export default contextStore;
export {Provider,Consumer};
在提供状态的App组件中,将需要借用context方法跨级传递的状态放在<Provider>
标签的props里面,标签内包裹上需要转递给的组件。
render() {
const {count,name} = this.state;
return <>
<Provider
value={{
count,
name,
setCount:this.setCount
}}
>
<Child />
</Provider>
</>
}
这样在Child
组件里面就不需要做任何处理,SubChild
组件就可以直接拿到数据。当然,如果Child
组件想要用这些数据,还是可以直接用this.props
取到。取数据有两种方式,分别是使用<Consumer>
标签获取和通过contextType
静态属性声明。
通过<Consumer>
,标签内包裹需要返回的虚拟DOM结构,需要的数据要从context中解构出来。
render() {
return <Consumer>
{(context) => {
const { count, name } = contextStore;
return <div>
<p>count:{count}</p>
<p>name:{name}</p>
</div>
}}
</Consumer>
}
还有一种方式,就是通过声明Component
类自带的静态属性contextType
来告诉类组件从哪里获取数据
class SubChild extends Component {
static contextType = contextStore;// contextType ,告诉当前组件,当前SubChild组件中context值,从上面写的context.hs文件导出的contextStore实例中获取,也就是可以理解为 let this.context = contextStore。
render() {
const { count, name, setCount } = this.context; //这个this.context就是类组件自己的context了,注意不要跟上面的contextStore搞混
return <div>
<p>count:{ count}</p>
<p>name:{name }</p>
<button onClick={()=>{
setCount(count +1);
}}>递增</button>
</div>
}
}
PS:不推荐直接使用context处理跨组件通信。context一般是给第三方库用的,后续会通过第三方库来处理跨级通信问题。
类组件的生命周期
挂载阶段
组件创建 => 把组件创建的虚拟DOM => 生成真实DOM =>添加到我们的DOM树中
-
constructor
:组件初始化 -
static getDerivedStateFromProps(props)
:将传递进来的props关联到state里面,注意这个函数一定要有返回值 -
componentDidMount
:生成真实的DOM节点,通常在这个阶段处理副作用(请求) -
render
:组件完成挂载,虚拟DOM已经生成了真实DOM
class Child extends Component {
constructor(props){
super(props);
console.log(0,"组件初始化");
}
// 将props 关联到state中
static getDerivedStateFromProps(props){
//console.log(this,props);该方法中没有 this
console.log(1," 将props 关联到state中");
return { //该生命周期函数必须有返回值,返回值中保存的是从 props 中 要关联到 state 中数据
info: "要关联的数据"
}
}
state={
count: 1
}
componentDidMount(){
console.log(3,"组件挂载完成,虚拟DOM,已经生成了真实DOM");
}
render() {
console.log(2,"生成虚拟DOM",this.state);
const {count} = this.state;
return <div>
<p>{count}</p>
<button onClick={()=>{
this.setState({
count: count + 1
})
}}>递增</button>
</div>
}
}
更新阶段
组件状态更新 => 组件重新渲染,从setstate之后组件就开始更新,一直到完成对真实的DOM节点更新
static getDerivedStateFromProps(props, state)
:这是一个静态方法,用来从props冲获取最新的state
shouldComponentUpdate()
:判断组件是否需要更新,以及哪些地方需要更新
render()
:重新渲染
getSnapshotBeforeUpdate()
:已经完成了新的虚拟DOM节点构建,但是没有更新到真实DOM,用来获取到更新前的DOM快照
componentDidUpdate()
:组件更新完成
class Child extends Component {
// 将props 关联到state中
static getDerivedStateFromProps(props){
//console.log(this,props);该方法中没有 this
console.log(1," 将props关联到state中");
return { //该生命周期函数必须有返回值,返回值中保存的是从 props 中 要关联到 state 中数据
info: "要关联的数据"
}
}
state={
count: 1
}
shouldComponentUpdate(nextProps,nextState){
//nextProps 更新后的props, nextState 更新后的state
//console.log(nextState,this.state);
console.log(2,"判断组件是否需要更新");
return true; //返回值为 true 则继续执行更新,返回值为false 则不执行更新,后续的生命周期函数,也不会执行
}
getSnapshotBeforeUpdate(){
//已经完成新的虚拟DOM的构建,但是还未更新真实DOM
//用来获取更新前的DOM快照
console.log(4,"已经完成新的虚拟DOM的构建,但是还未更新真实DOM");
let box = document.querySelector("#box");
return box.innerHTML;//改返回值会传递给componentDidUpdate的prevDOM参数
}
componentDidUpdate(prevProps,prevState,prevDOM){
// console.log(prevDOM);
console.log(5,"组件更新完成");
}
render() {
console.log(3,"生成虚拟DOM",this.state);
const {count} = this.state;
return <div id="box">
<p>{count}</p>
<button onClick={()=>{
this.setState({
count: count + 1
})
}}>递增</button>
</div>
}
}
卸载阶段
componentWillUnmount()
:组件即将销毁,可以在这个阶段清空各种监听事件。
class Child extends Component {
state={
count: 1
}
componentDidMount(){
let getSize = ()=>{
let size = document.querySelector("#size");
size.innerHTML = window.innerWidth;
}
getSize();
window.onresize = getSize;
}
componentWillUnmount(){
window.onresize = null;
}
render() {
const {count} = this.state;
return <div id="box">
<p>{count}</p>
<button onClick={()=>{
this.setState({
count: count + 1
})
}}>递增</button>
<div id="size">0</div>
</div>
}
}
受控组件
Vue
给我提供了一个语法糖v-model
来实现表单与数据的双向绑定,但是React
比较懒,专注于渲染视图,并没有提供这样的语法糖,需要我们通过state
和props
手动声明,声明好之后就形成了一个双向绑定的受控组件。会用到这个受控组件的场景也很类似,需要输入的表单或者需要打钩的CheckBox
。
-
value
: 将组件的状态,赋值给控件的value
属性,通过onChange
事件监听控件的value
属性发生变化,再通过回调函数赋值给组件的state
-
checked
:将组件的状态,赋值给控件的checked
属性,通过onChange
事件监听控件的checked
属性发生变化,再通过回调函数赋值给组件的state
class App extends Component {
state={
val:""
}
componentDidMount(){
let text1 = document.querySelector("#text1");
text1.oninput = function(){
console.log(this.value);
}
}
render() {
const {val} = this.state;
return <div>
<input
type="text"
value={val}
onChange={({target})=>{
this.setState({
val: target.value
})
}}
/>
<input type="text" id="text1" />
<input type="text" id="text2" defaultValue="初始值" />
<p>{val}</p>
<button onClick={()=>{
this.setState({
val:""
})
}}>重置</button>
</div>
}
}
非受控组件
-
需要设置初始值,只设置初始值,并不希望该控件受控,不要用
value|checked
,因为只要用value|checked
的话react
就认为我们做受控组件。所以这里要用defaultValue
和defaultChecked
<input type="text" defaultValue={val} onChange={({target})=>{ this.setState({ val: target.value }) }} />
-
初始值,直接留空。
实例
// App.js
import { Component } from "react";
import "./index.css";
import AddMeaasge from "./AddMeaasge";
import MessageList from "./MessageList";
class App extends Component{
state = {
data:[{
id:0,
name:"宋成昊",
msg:"作业暗号:海哥真帅"
}]
};
addMeaasge = (newName, newMsg) => {
const {data} = this.state;
data.push({
id:Date.now(),
name:newName,
msg:newMsg
});
this.setState({
data
})
};
remove = (id) => {
const {data} = this.state;
this.setState({
data:data.filter(id => {return data.id = !id })
})
}
render(){
let { data } = this.state
return <section className="wrap">
<h2 className="title">留言板</h2>
<AddMeaasge
addMeaasge = {this.addMeaasge}
/>
<MessageList
data = { data }
remove = { this.remove }
/>
</section>
};
}
export default App;
// MessageList.js
import { Component } from "react";
class MessageList extends Component{
render(){
const { data,remove } = this.props
return <ul className="messageList">
{data.map((item,index) => {
return <li key={index}>
<h3>{item.name}</h3>
<p>{item.msg}</p>
<a onClick={()=>{
const id =item.id
remove(id)
}}>删除</a>
</li>
})}
</ul>
};
};
export default MessageList
//AddMeaasge.js
import { Component } from "react";
class AddMeaasge extends Component{
state={
newName:"",
newMsg:""
}
render(){
const {newName, newMsg} = this.state
const { addMeaasge } = this.props
return <div className="addMessage">
<input
type="text"
placeholder="请输入昵称"
autoComplete="off"
value={newName}
onChange={({target})=>{
this.setState({
newName:target.value
})
}}
/>
<textarea
placeholder="请输入留言"
value={newMsg}
onChange={({target})=>{
this.setState({
newMsg:target.value
})
}}
></textarea>
<button
onClick={() => {
addMeaasge(newName,newMsg);
this.setState({
newName:"",
newMsg:""
})
}}
>提交留言</button>
</div>
}
};
export default AddMeaasge
// index.css
// 略
最终效果:
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!