类型验证propTypes/defaultProps,子传父(props => 函数),React插槽效果实现及作用域插槽,非父子通信,Context使用

144 阅读3分钟

参数类型验证propTypes

propTypes使用

import { Component } from "react";
import Banner from "./Banner";
import Recommend from "./Recommend";
export class App extends Component {
  constructor(props) {
    super();
    this.state = {
      banner: [],
      recommend: [],
    };
  }
  componentDidMount() {
    fetch("http://123.207.32.32:8000/home/multidata")
      .then((res) => res.json())
      .then((res) => {
        const { banner, recommend } = res.data;
        this.setState({
          banner: banner.list,
          recommend: recommend.list,
        });
      });
  }
  render() {
    const { banner, recommend } = this.state;
    return (
      <div>
        <Banner banner={banner} title="t_zm" />
        {/* `title` of type `number` supplied to `Banner`, expected `string`. */}
        <Banner banner={banner} title={123} />
        {/* 子组件有默认值则使用默认值 */}
        <Banner />
        <Recommend recommend={recommend} />
      </div>
    );
  }
}
export default App;
import React, { Component } from "react";
import PropTypes from "prop-types";

export class Banner extends Component {
  // 默认值(方式2)
  static defaultProps = {
    title: "this is a static title",
    banner: [],
  };
  render() {
    const { banner, title } = this.props;
    return (
      <div>
        <h1>{title}</h1>
        <ul>
          {banner.map((item) => {
            return <li key={item.acm}>{item.title}</li>;
          })}
        </ul>
      </div>
    );
  }
}

//Banner.propTypes是小写
Banner.propTypes = {
  title: PropTypes.string,
  banner: PropTypes.array,
};

// 默认值(方式1)
Banner.defaultProps = {
  title: "this is a title",
  banner: [],
};
export default Banner;

子传父

和Vue,小程序不同,React没有自定义事件,而是通过父组件给子组件传递一个函数,在子组件中调用这个函数即可

import { Component } from "react";
import AddCount from "./AddCount";
export class App extends Component {
  constructor(props) {
    super();
    this.state = {
      count: 100,
    };
  }
  changeCount(count) {
    this.setState({
      count: this.state.count + count,
    });
  }
  render() {
    const { banner, recommend, count } = this.state;
    return (
      <div>
        <div>
          <h1>当前计数:{count}</h1>
          {* 传给子组件的是一个函数,子组件可以通过this.props.addClick()调用,传递相应的参数给父组件 *}
          <AddCount addClick={(count) => this.changeCount(count)} />
        </div>
      </div>
    );
  }
}
export default App;
import React, { Component } from "react";
export class AddCount extends Component {
  addCount(count) {
    this.props.addClick(count);
  }
  render() {
    return (
      <div>
        <button onClick={(e) => this.addCount(1)}>+1</button>
        <button onClick={(e) => this.addCount(5)}>+5</button>
        <button onClick={(e) => this.addCount(10)}>+10</button>
      </div>
    );
  }
}
export default AddCount;

组件通信案例练习

tabbar实现

image.png

import React, { Component } from "react";
import TabControl from "./组件通信案例类型navbar/TabControl";

export class MyApp extends Component {
  constructor() {
    super();
    this.state = {
      titles: ["唱歌", "跳舞", "篮球"],
      tabIndex: 0,
    };
  }
  changeIndexShowContent(index) {
    this.setState({
      tabIndex: index,
    });
  }
  render() {
    const { titles, tabIndex } = this.state;
    return (
      <div>
        <TabControl titles={titles} changeIndexShowContent={(index) => this.changeIndexShowContent(index)} />
        <h1>根据子组件index展示不同数据:{titles[tabIndex]}</h1>
      </div>
    );
  }
}
export default MyApp;
import React, { Component } from "react";
import "./style.css";

export class TabControl extends Component {
  constructor() {
    super();
    this.state = {
      currentIndex: 0,
    };
  }
  itemClick(index) {
    this.setState({
      currentIndex: index,
    });
    this.props.changeIndexShowContent(index);
  }
  render() {
    const { titles } = this.props;
    const { currentIndex } = this.state;
    return (
      <div className="nav-bar">
        {titles.map((item, index) => {
          return (
            <div className="nav-bar-item" key={index} onClick={() => this.itemClick(index)}>
              <span className={`text ${currentIndex === index ? "active" : ""}`}>{item}</span>
            </div>
          );
        })}
      </div>
    );
  }
}
export default TabControl;
.nav-bar {
  display: flex;
  height: 40px;
  line-height: 40px;
}
.nav-bar-item {
  text-align: center;
  flex: 1;
}
.text {
  width: 30px;
  padding: 3px;
}
.text.active {
  color: red;
  border-bottom: 3px solid red;
}

React中插槽效果实现

  • children方案(弊端:通过索引值获取传入的元素容易出错,不能精准的获取传入的元素)

image.png

import React, { Component } from "react";
import NavBar from "./组件的插槽实现/nav-bar";
export class NavBarApp extends Component {
  render() {
    return (
      <div>
        {/* <NavBar>
          <button>按钮</button>
          <h2>标题</h2>
          <i>斜体文字</i>
        </NavBar> */}

        {/* 一个的情况,children就不是数组了,而是对象 */}
        <NavBar>
          <button>btn</button>
          {/*如果子组件NavBar中
          NavBar.propTypes = {
            children: PropTypes.element, //children只能传递一个
           };这么写,则父组件只能传递一个元素,传递多个会报下面的错误
          Failed prop type: Invalid prop `children` of type `array` supplied to `NavBar`, expected a single ReactElement.
          如果子组件是children: PropTypes.array相反
       */}
          <button>btn</button>
        </NavBar>
      </div>
    );
  }
}
export default NavBarApp;
import React, { Component } from "react";
import PropTypes from "prop-types";
import "./style.css";

export class NavBar extends Component {
  render() {
    const { children } = this.props;
    return (
      <div className="nav-bar">
        {/* children多个的情况,array */}
        {/* <div className="left">{children[0]}</div>
        <div className="center">{children[1]}</div>
        <div className="right">{children[2]}</div> */}

        {/* 一个的情况,children就不是数组了,而是对象 */}
        <div className="center">{children}</div>
      </div>
    );
  }
}

NavBar.propTypes = {
  children: PropTypes.element, //children只能传递一个
  //   children: PropTypes.array, //children传递多个,是array
};
export default NavBar;
h1,
h2,
body {
  margin: 0;
  padding: 0;
}
.nav-bar {
  display: flex;
  height: 40px;
  line-height: 40px;
  justify-content: center;
  align-items: center;
}
.left,
.right {
  width: 80px;
  background-color: blueviolet;
  text-align: center;
}
.center {
  flex: 1;
  text-align: center;
  background-color: aquamarine;
}
  • props属性
import React, { Component } from "react";
import NavBar from "./组件的插槽实现/nav-bar";
import NavBarTwo from "./组件的插槽实现/nav-bar-props实现";
export class NavBarApp extends Component {
  render() {
    return (
      <div>
        {/* 1.使用children实现插槽 */}
        <NavBar>
          <button>按钮</button>
          <h2>标题</h2>
          <i>斜体文字</i>
        </NavBar>

        {/* 2.使用props实现插槽 */}
        <NavBarTwo leftSlot={<div>按钮2</div>} centerSlot={<h2>zm</h2>} rightSlot={<i>斜体2</i>} />
      </div>
    );
  }
}
export default NavBarApp;
import React, { Component } from "react";
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>
    );
  }
}
export default NavBarTwo;
  • 作用域插槽

React中实现作用域插槽
props + 回调函数

image.png

import React, { Component } from "react";
import TabControl from "./组件插槽-实现作用域插槽/TabControl";

export class ScopeSlotApp extends Component {
  constructor() {
    super();
    this.state = {
      titles: ["唱歌", "跳舞", "篮球"],
      tabIndex: 0,
    };
  }
  changeIndexShowContent(index) {
    this.setState({
      tabIndex: index,
    });
  }
  getTabItem(item) {
    if (item === "唱歌") {
      return <span>{item}</span>;
    } else if (item === "跳舞") {
      return <button>{item}</button>;
    } else {
      return <i>{item}</i>;
    }
  }
  render() {
    const { titles, tabIndex } = this.state;
    return (
      <div>
        <TabControl
          itemType={(item) => this.getTabItem(item)}
          titles={titles}
          changeIndexShowContent={(index) => this.changeIndexShowContent(index)}
        />
        <h1>根据子组件index展示不同数据:{titles[tabIndex]}</h1>
      </div>
    );
  }
}
export default ScopeSlotApp;
import React, { Component } from "react";
import "./style.css";

export class TabControl extends Component {
  constructor() {
    super();
    this.state = {
      currentIndex: 0,
    };
  }
  itemClick(index) {
    this.setState({
      currentIndex: index,
    });
    this.props.changeIndexShowContent(index);
  }
  render() {
    const { titles, itemType } = this.props;
    const { currentIndex } = this.state;
    return (
      <div className="nav-bar">
        {titles.map((item, index) => {
          return (
            <div className="nav-bar-item" key={index} onClick={() => this.itemClick(index)}>
              {/* <span className={`text ${currentIndex === index ? "active" : ""}`}>{item}</span> */}

              {/* 显示什么(itemType()的返回值)由父组件决定,把item通过 父组件传递过来的回调函数itemType() 传递给父组件 */}
              <span className={`text ${currentIndex === index ? "active" : ""}`}>{itemType(item)}</span>
            </div>
          );
        })}
      </div>
    );
  }
}
export default TabControl;

Context的使用

{...this.props}的使用

  • spread props/attributes 官网 image.png

  • 应用场景

image.png

  • Context基本使用

image.png

  • Context相关API

image.png

image.png

代码

image.png

import React, { Component } from "react";
import Home from "./非父子组件通信-Context/Home";
import ThemeContext from "./非父子组件通信-Context/context/theme-context";
import UserContext from "./非父子组件通信-Context/context/user-context";
export class ContextApp extends Component {
  constructor() {
    super();
    this.state = {
      ikun: { name: "zm", age: 21 },
    };
  }
  render() {
    const { ikun } = this.state;
    return (
      <div>
        <h1>ContextApp</h1>
        {/* 1.给Home传递数据 */}
        {/* open is error 因为Home子组件用到了this.context,是undefined,可以使用defaultValue*/}
        {/* 这样写是使用context默认值
            const ThemeContext = React.createContext({ color: "default_red", size: "18" });
            const UserContext = React.createContext({ nickName: "default_ww", age: 3 });
        */}
        <Home name="dhs" age={18} />
        <Home name={ikun.name} age={ikun.age} />
        <Home {...ikun} />

        {/* 2.普通的Home */}
        {/* 第二步操作: 通过ThemeContext中Provider中value属性为后代提供数据 */}
        <UserContext.Provider value={{ nickName: "ww", age: 18 }}>
          <ThemeContext.Provider value={{ color: "red", size: "18" }}>
            <Home {...ikun} />
          </ThemeContext.Provider>
        </UserContext.Provider>
      </div>
    );
  }
}
export default ContextApp;
import React from "react";
// 1.创建一个Context
const ThemeContext = React.createContext({ color: "default_red", size: "18" });
export default ThemeContext;
import React from "react";
// 1.创建一个Context
const UserContext = React.createContext({ nickName: "default_ww", age: 3 });
export default UserContext;
import React, { Component } from "react";
import HomeInfo from "./HomeInfo";
import HomeBanner from "./HomeBanner";
export class Home extends Component {
  render() {
    const { name, age } = this.props;
    return (
      <div>
        <p>
          {name}-{age}
        </p>
        <div>
          <HomeInfo />
          <HomeBanner />
        </div>
      </div>
    );
  }
}
export default Home;
import React, { Component } from "react";
import ThemeContext from "./context/theme-context";
import UserContext from "./context/user-context";
export class HomeInfo extends Component {
  render() {
    // 第四步:获取数据,并且使用数据
    console.log("this.context=> ", this.context);
    return (
      <div>
        <h2>HomeInfo:{this.context.color}</h2>
        <UserContext.Consumer>
          {(value) => {
            return <h2>Info User:{value.nickName}</h2>;
          }}
        </UserContext.Consumer>
      </div>
    );
  }
}
// 第三步:设置组件的contextType为某一个Context
HomeInfo.contextType = ThemeContext;
export default HomeInfo;
import ThemeContext from "./context/theme-context";
function HomeBanner() {
  return (
    <div>
      <span>HomeBanner</span>
      {/* 函数式组件中使用Context共享数据 , 回调函数*/}
      <ThemeContext.Consumer>
        {(value) => {
          return <h2>{value.color}</h2>;
        }}
      </ThemeContext.Consumer>
    </div>
  );
}
export default HomeBanner;