类组件
类组件的定义有如下要求:
- 组件的名称是大写字符开头(无论类组件还是函数组件)
- 类组件需要继承自 React.Component
- 类组件必须实现render函数
使用class定义一个组件:
- constructor是可选的,我们通常在constructor中初始化一些数据;
- this.state中维护的就是我们组件内部的数据(页面渲染需要的数据);
- render() 方法是 class 组件中唯一必须实现的方法;
render函数的返回值
当 render 被调用时,它会检查 this.props 和 this.state(通过setState函数改变) 的变化并返回以下类型
- React 元素(通常通过 JSX 创建。也就是通过 Babel编译成react.createElement)
- 数组或者fragments [123,243] 会对数组进行遍历 然后依次显示出来
- Portals 可以渲染子节点到不同Dom树中
- 字符串或数值类型:它们在 DOM 中会被渲染为文本节点
- 布尔类型或 null:什么都不渲染。
函数组件
函数组件是使用function来进行定义的函数,只是这个函数会返回和类组件中render函数返回一样的内容。
函数组件有自己的特点(有了hooks 就可以完美解决这些问题)
- 没有生命周期,也会被更新并挂载,但是没有生命周期函数;
- this关键字不能指向组件实例(因为没有组件实例);
- 没有内部状态(state)
生命周期
最常用的生命周期函数图
- componentDidMount()(组件被渲染到DOM: 被挂载到DOM)
- 依赖于DOM操作可以在这里面进行
- 发送网络请求
- 可以在这处订阅一些东西(事件总线)会在componentWillUnmount取消订阅
- componentDidUpdate() 会在更新后会被立即调用,首次渲染不会执行此方法
- 当组件更新后,可以在此处对 DOM 进行操作
- 如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求;(例如,当 props 未发生变化时,则不会执行网 络请求)。
- componentWillUnmount 会在组件卸载及销毁之前直接调用
- 在此方法中执行必要的清理操作;
- 例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等;
不常用的生命周期
-
getDerivedStateFromProps:state 的值在任何时候都依赖于 props时使用;该方法返回一个对象 来更新state;
-
getSnapshotBeforeUpdate:在React更新DOM 之前回调的一个函数,可以获取DOM更新前的一些信息(比如说滚动位置);可以在componentDidUpdate钩子中获取到他返回的参数
-
shouldComponentUpdate:该生命周期函数很 常用,返回一个true/false 来是否进行渲染
import React from "react"
class HelloWorld extends React.Component {
// 1.构造方法: constructor
constructor() {
console.log("HelloWorld constructor")
super()
this.state = {
message: "Hello World"
}
}
changeText() {
this.setState({ message: "你好啊, 李银河" })
}
// 2.执行render函数
render() {
console.log("HelloWorld render")
const { message } = this.state
return (
<div>
<h2>{message}</h2>
<p>{message}是程序员的第一个代码!</p>
<button onClick={e => this.changeText()}>修改文本</button>
</div>
)
}
// 3.组件被渲染到DOM: 被挂载到DOM
componentDidMount() {
console.log("HelloWorld componentDidMount")
}
// 4.组件的DOM被更新完成: DOM发生更新
componentDidUpdate(prevProps, prevState, snapshot) {
console.log("HelloWorld componentDidUpdate:", prevProps, prevState, snapshot)
}
// 5.组件从DOM中卸载掉: 从DOM移除掉
componentWillUnmount() {
console.log("HelloWorld componentWillUnmount")
}
// 不常用的生命周期补充
shouldComponentUpdate() {
return true
}
getSnapshotBeforeUpdate() {
console.log("getSnapshotBeforeUpdate")
return {
scrollPosition: 1000
}
}
}
export default HelloWorld
组件间的通讯
父传子
- 父组件通过 属性=值 的形式来传递给子组件数据;
- 子组件通过 props 参数获取父组件传递过来的数据;
- 可以通过 prop-types 库来进行参数验证
- 可以使用defaultProps库 来进行参数默认值的设置
export class MainBanner extends Component {
// 可以把默认值写成类中的 static defaultProps 对传入进来的参数赋默认值
// static defaultProps = {
// banners: [],
// title: "默认标题"
// }
// 当类中没有内部数据state的时候 可以补血constructor 他内部自动给this赋props
constructor(props) {
super(props)
this.state = {}
}
render() {
// console.log(this.props)
const { title, banners } = this.props
return (
<div className='banner'>
<h2>封装一个轮播图: {title}</h2>
<ul>
{
banners.map(item => {
return <li key={item.acm}>{item.title}</li>
})
}
</ul>
</div>
)
}
}
// MainBanner传入的props类型进行验证
MainBanner.propTypes = {
banners: PropTypes.array.isRequired,
title: PropTypes.string
}
// MainBanner传入的props的默认值
MainBanner.defaultProps = {
banners: [],
title: "默认标题"
}
子传父
- 通过props传递消息,只是让父组件给子组件传递一个回调函数,在子组件中调用这个函数即可;
子组件
export class SubCounter extends Component {
subCount(count) {
this.props.subClick(count)
}
render() {
return (
<div>
// 通过箭头函数 给subCount方法传递参数
<button onClick={e => this.subCount(-1)}>-1</button>
<button onClick={e => this.subCount(-5)}>-5</button>
<button onClick={e => this.subCount(-10)}>-10</button>
</div>
)
}
}
// 夫组件
export class App extends Component {
constructor() {
super()
this.state = {
counter: 100
}
}
changeCounter(count) {
this.setState({ counter: this.state.counter + count })
}
render() {
const { counter } = this.state
return (
<div>
<h2>当前计数: {counter}</h2>
<SubCounter subClick={(count) => this.changeCounter(count)}/>
</div>
)
}
}
React中的插槽(slot)
- 组件中的children子组件
- props属性传递React元素
- children方式实现插槽 注意事项 从props获取到children 写在组件闭合标签内部的元素。 如果他是多个元素 它就返回一个数组 如果只有一个元素 他就返回元素对象
- 数组:通过数组索引进行使用(比较麻烦)
- 对象 :直接赋值 在react 源码之中createElemnt函数中 他接收的children 参数 会通过arguments来判断children参数长度
- 如果等于1的话,那就直接赋值给对象
- 如果大于1的话,那就会形成新的数组 把children逐步push里面
<NavBar>
<button>按钮</button>
<h2>哈哈哈</h2>
<i>斜体文本</i>
</NavBar>
export class NavBar extends Component {
render() {
const { children } = this.props
return (
<div className='nav-bar'>
<div className="left">{children[0]}</div>
<div className="center">{children[1]}</div>
<div className="right">{children[2]}</div>
</div>
)
}
}
补充点 :在给组件传递参数时候 可以用属性扩展 React网址:zh-hans.reactjs.org/docs/jsx-in…
function App1() {
return <Greeting firstName="Ben" lastName="Hector" />;
}
他俩是等价的
function App2() {
const props = {firstName: 'Ben', lastName: 'Hector'};
return <Greeting {...props} />;}
- props实现插槽 给组件属性传入一个元素
<NavBarTwo
leftSlot={btn}
centerSlot={<h2>呵呵呵</h2>}
rightSlot={<i>斜体2</i>}
/>
export class NavBarTwo extends Component {
render() {
const { leftSlot, centerSlot, rightSlot } = this.props
return (
<div className='nav-bar'>
<div className="left">{leftSlot}</div>
<div className="center">{centerSlot}</div>
<div className="right">{rightSlot}</div>
</div>
)
}
}
// 可以使用箭头函数来进行作用域插槽(插槽的传递参数)
getTabItem(item) {
if (item === "流行") {
return <span>{item}</span>
} else if (item === "新款") {
return <button>{item}</button>
} else {
return <i>{item}</i>
}
}
render() {
return (
<div className='app'>
<TabControl
// itemType={item => <button>{item}</button>}
itemType={item => this.getTabItem(item)}
/>
</div>
)
}
return (
<div className='tab-control'>
{
titles.map((item, index) => {
return (
<div
className={`item ${index === currentIndex?'active':''}`}
key={item}
onClick={e => this.itemClick(index)}
>
{/* <span className='text'>{item}</span> */}
{itemType(item)}
</div>
)
})
}
</div>
)
非父子组件数据共享
- React提供了一个API:Context;
- Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props;
- Context 设计目的是为了共享那些对于一个组件树而言是 “全局”的数据,例如当前认证的用户、主题或首选语言;
context相关API
-
React.createContext
- 创建一个需要共享的Context对象:
- 如果一个组件订阅了Context,那么这个组件会从离自身最近的那个匹配的 Provider 中读取到当前的context值;
- defaultValue是组件在顶层查找过程中没有找到对应的Provider,那么就使用默认值
-
Context.Provider
- 每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化
- Provider 接收一个 value 属性,传递给消费组件;
- 一个 Provider 可以和多个消费组件有对应关系;
- 多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据;
- 当 Provider 的value 值发生变化时,它内部的所有消费组件都会重新渲染;
-
Class.contextType
- 挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象:
- 这能让你使用 **this.context **来消费最近 Context 上的那个值;
- 你可以在任何生命周期中访问到它,包括 render 函数中;
-
Context.Consumer
- 这里,React 组件也可以订阅到 context 变更。这能让你在 函数式组件 中完成订阅 context。
- 这里需要 函数作为子元素(function as child)这种做法;
- 这个函数接收当前的 context 值,返回一个 React 节点;
-
什么时候使用Context.Consumer呢?
- 当使用value的组件是一个函数式组件时;
- 当组件中需要使用多个Context时;
{/* 第二步操作: 通过ThemeContext中Provider中value属性为后代提供数据 */}
// 1.创建Context
const ThemeContext = React.createContext({ color: "blue", size: 10 })
const UserContext = React.createContext()
<UserContext.Provider value={{nickname: "kobe", age: 30}}>
<ThemeContext.Provider value={{color: "red", size: "30"}}>
<Home {...info}/>
</ThemeContext.Provider>
</UserContext.Provider>
{/* 没有被context所包裹的 就会使用默认值*/}
<Profile/>
export class HomeInfo extends Component {
render() {
// 4.第四步操作: 获取数据, 并且使用数据
console.log(this.context)
return (
<div>
<h2>HomeInfo: {this.context.color}</h2>
<UserContext.Consumer>
{
value => {
return <h2>Info User: {value.nickname}</h2>
}
}
</UserContext.Consumer>
</div>
)
}
}
// 3.第三步操作: 设置组件的contextType为某一个Context
HomeInfo.contextType = ThemeContext