React(三)
- 一、脚手架安装和创建
 - 1.安装脚手架
 - 2.创建脚手架
 - 3.看看脚手架目录
 - 4.运行脚手架
 
- 二、脚手架下从0开始写代码
 - 三、组件化
 - 1.类组件
 - 2.函数组件
 
- 四、React的生命周期
 - 1.认识生命周期
 - 2.图解生命周期
 - (1)Constructor
 - (2)componentDidMount
 - (3)componentDidUpdate
 - (4)componentWillUnmount
 
- 3.演示生命周期
 - 4.不常用的生命周期
 
- 五、父子组件通信
 - 1.父传子props接收
 - 2.props接收数据类型限制和默认值
 - 3.子传父用函数
 - 4.案例练习
 
- 六、React插槽效果
 - 1.通过props.children传递
 - 2.通过props直接传递
 - 3.作用域插槽
 
一、脚手架安装和创建
首先安装Node:保姆级别教程
1.安装脚手架
在git bash中输入: npm install create-react-app -g,然后输入create-react-app --version,如果能正常显示版本号,那么安装就成功了。
2.创建脚手架
目录下右键 => git bash => create-react-app 项目名 => 回车等几分钟就欧了。
 注意项目名不能包含大写字母。
3.看看脚手架目录

4.运行脚手架
脚手架目录下 => npm run start,然后就可以看到非常帅气的大花。
二、脚手架下从0开始写代码
没啥用的先删了,我们自己搭建src中的文件:
 
 好,那么接下来我们重新写一下src里面的文件:

index.js
//重写react代码,并且通过react渲染出来对应的内容
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(<App/>);
 
App.jsx
import React from 'react';
import HelloReact from './Components/HelloReact';
class App extends React.Component {
    constructor() {
        super();
        this.state = {
            name:'zzy'
        }
    }
    render() {
        return (
            <div>
                <h2>奥里给</h2>
                <button>按钮</button>
                <HelloReact/>
            </div>
        )
    }
}
export default App;
 
HelloReact.jsx
import React from 'react';
class HelloReact extends React.Component {
    constructor() {
        super();
        this.state = {
            name: 'react'
        }
    }
    render() {
        return (
            <div>
                <h2>Hello React!</h2>
            </div>
        )
    }
}
export default HelloReact
 
三、组件化
1.类组件
类组件的定义有如下要求:
- 组件的名称是大写字符开头(无论类组件还是函数组件)
 - 类组件需要继承自 
React.Component - 类组件必须实现
render函数 
使用class定义一个组件:
- constructor是可选的,我们通常在constructor中初始化一些数据;
 this.state中维护的就是我们组件内部的数据;render()方法是 class 组件中唯一必须实现的方法;
class App extends React.Component {
    constructor() {
        super();
        this.state = {
            name:'zzy'
        }
    }
    render() {
        return [
            <div>
                <h2>奥里给</h2>
                <button>按钮</button>
                <HelloReact/>
            </div>,
            <div></div>
        ]
    }
}
 
render函数的返回值可以是什么?
- React 元素:
通常通过 JSX 创建。
例如,<div />会被 React 渲染为 DOM 节点,<MyComponent />会被 React 渲染为自定义组件;
无论是<div />还是<MyComponent />均为 React 元素(通过creatElement创建出来的东西)。 - 数组或 fragments:使得 render 方法可以返回多个元素。
 - Portals:可以渲染子节点到不同的 DOM 子树中。
 - 字符串或数值类型:它们在 DOM 中会被渲染为文本节点
 - 布尔类型或 null:什么都不渲染
 
2.函数组件
函数组件是使用function来进行定义的函数,只是这个函数会返回和类组件中render函数返回一样的内容。
函数组件有自己的特点(当然,后面我们会讲hooks,就不一样了):
- 没有生命周期,也会被更新并挂载,但是没有生命周期函数;
 - 没有
this(组件实例); - 没有内部状态(
state); 
我们来定义一个函数组件:
//函数式组件
function App() {
    //返回的东西和render返回的是一样的。
    return <h1>我是一个函数组件</h1>
}
export default App;
 
四、React的生命周期
1.认识生命周期
生命周期的概念和vue中是一样的,只不过在React中钩子更少一些。
React内部为了告诉我们当前处于哪些阶段,会对我们组件内部实现的某些函数进行回调,这些函数就是生命周期函数:
- 比如实现componentDidMount函数:组件已经挂载到DOM上时,就会回调;
 - 比如实现componentDidUpdate函数:组件已经发生了更新时,就会回调;
 - 比如实现componentWillUnmount函数:组件即将被移除时,就会回调;
 
我们可以在这些回调函数中编写自己的逻辑代码,来完成自己的需求功能;
 我们谈React生命周期时,主要谈的类的生命周期,因为函数式组件是没有生命周期函数的;(后面我们可以通过hooks来模拟一些生命周期的回调)
2.图解生命周期

 这张图画的还是非常不错的,在这里插入代码片
(1)Constructor
如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。
 constructor中通常只做两件事情:
 1、通过给 this.state 赋值对象来初始化内部的state;
 2、为事件绑定实例(this);
(2)componentDidMount
componentDidMount() 会在组件挂载后(插入 DOM 树中)立即调用。
 componentDidMount中通常进行哪里操作呢?
 依赖于DOM的操作可以在这里进行;
 在此处发送网络请求就最好的地方;(官方建议)
 可以在此处添加一些订阅(会在componentWillUnmount取消订阅);
(3)componentDidUpdate
componentDidUpdate() 会在更新后会被立即调用,首次渲染不会执行此方法。
 当组件更新后,可以在此处对 DOM 进行操作;
 如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求;(例如,当 props 未发生变化时,则不会执行网络请求)。
(4)componentWillUnmount
componentWillUnmount() 会在组件卸载及销毁之前直接调用。
 在此方法中执行必要的清理操作;
 例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等;
3.演示生命周期
我们创建累组件App,并将HelloReact组件作为它的子组件:
App组件:
class App extends React.Component {
    constructor() {
        console.log('APP-constructor')
        super();
        this.state = {
            name: 'zzy'
        }
    }
    changeData() {
        this.setState({
            name: 'ht'
        })
    }
    render() {
        console.log('App-render');
        const { name } = this.state;
        return (
            <div>
                <h2>{name}</h2>
                <button onClick={() => this.changeData()}>点击修改数据</button>
                {name == 'zzy' && <HelloReact />}
            </div>
        )
    }
    componentDidMount() {
        console.log('App-componentDidMount')
    }
    componentDidUpdate() {
        console.log('App-componentDidUpdate')
    }
}
 
HelloReact组件
class HelloReact extends React.Component {
    constructor() {
        console.log('HR-constructor')
        super();
        this.state = {
            name: 'react'
        }
    }
    render() {
        console.log('HR-render')
        return (
            <div>
                <h2>Hello React!</h2>
            </div>
        )
    }
    componentDidMount() {
        console.log('HR-componentDidMount')
    }
    componentDidUpdate() {
        console.log('HR-componentDidUpdate')
    }
    componentWillUnmount() {
        console.log('HR-componentWillUnmount')
    }
}
 
让我们看一下控制台的输出:
 
 不难看出生命周期的一个顺序:
 对于挂载来说:父组件constuctor => 父组件render => 子组件constructor => 子组件render => 子组件挂载完毕 => 父组件挂载完毕
对于更新来说,如果要让子组件从页面上消失,那么点击跟新执行父组件render函数后子组件会走销毁的钩子,然后走子组件更新完毕的钩子,和图是一样滴。
4.不常用的生命周期
请参考:React官方文档生命周期
 
五、父子组件通信
在了解React中的组件通信前,我们先搭建一个组件嵌套结构:
 
class App extends React.Component {
    render() {
        const { name } = this.state;
        return (
            <div>
                <Header/>
                <Main/>
                <Footer/>
            </div>
        )
    }
}
 
Footer和Header长得一样
class Header extends React.Component {
    render() {
        return (
            <div>
                <h2>Header</h2>
            </div>
        )
    }
}
 
class Main extends Component {
  render() {
    return (
      <div>
        <h2>Main</h2>
        <Banner/>
        <ProductList/>
      </div>
    )
  }
}
 
class Banner extends Component {
  render() {
    return (
      <div>Banner</div>
    )
  }
}
 
class ProductList extends Component {
  render() {
    return (
      <div>ProductList</div>
    )
  }
}
 
1.父传子props接收
这里我们主要研究Main给Banner或ProductList组件传数据,我们可以准备一些静态数据。
和vue一样,在子组件标签处写属性名,传变量用{},传其他用字符串。
class Main extends Component {
  constructor() {
    super();
    this.state = {
      banner: ['动作射击', '角色扮演', '策略运营'],
      productList: ['古墓丽影','镜之边缘','神秘海域','奥里给']
    }
  }
  render() {
    const { banner, productList } = this.state;
    return (
      <div>
        <h2>Main</h2>
        <Banner banner={banner} />
        <ProductList title="商品列表" productList={productList}/>
      </div>
    )
  }
}
 
然后子组件在constructor中使用super(props)继承父类的props属性,并把props这个属性设置为传进来的值。
当然啊,如果不写constructor,也能自动接收到props,这是一个小细节。
class ProductList extends Component {
  constructor(props) {
    console.log(props);
    super(props);
  }
  render() {
    console.log(this.props)
    const { title, productList } = this.props;
    return (
      <div>
        <div>{title}</div>
		<ul>
         {productList.map(item => {
           return <li key={item}>{item}</li>
         })}
       </ul>
	  </div>
    )
  }
}
 

 接下来我们就可以队商品列表和标题进行展示:
2.props接收数据类型限制和默认值
我们以Main组件和ProductList组件为例,将Main中的数据传给ProductList
class Main extends Component {
  constructor() {
    super();
    this.state = {
      productList: ['古墓丽影','镜之边缘','神秘海域','奥里给']
    }
  }
  render() {
    const { banner, productList } = this.state;
    return (
      <div>
        <h2>Main</h2>
        <ProductList age={18} title="商品列表" productList={productList}/>
        <ProductList age={10}/>
      </div>
    )
  }
}
 
接下来我们就可以在ProductList组件中对接收的数据进行类型和默认值的设置:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class ProductList extends Component {
  constructor(props) {
    console.log(props);
    super(props);
  }
  render() {
    console.log(this.props);
    const { age,title, productList } = this.props;
    return (
      <div>
        <h3>ProductList</h3>
        <div>{age}</div>
        <div>{title}</div>
        <ul>
          {productList.map(item => {
            return <li key={item}>{item}</li>
          })}
        </ul>
      </div>
    )
  }
}
 
//props接收数据类型的限定
ProductList.propTypes = {
  title: PropTypes.string, 
  age: PropTypes.number.isRequired, //必须是数字类型,必须有
  productList: PropTypes.array,
}
//props接收数据的默认值
ProductList.defaultProps = {
  title: '我的网页的干活',
  productList: [],
}
 
如上,首先引入PropTypes
import PropTypes from 'prop-types';
 
然后在类组件名字后面设置属性propTypes。
 默认值则设置属性defaultProps。
其他细节可以翻看官方文档:props默认值大全
 有些新东西比如我们现在可以通过static关键字直接在组件中写:
class Greeting extends React.Component {
  static defaultProps = {
    name: 'zzy'
  }
  render() {
    return (
      <div>Hello, {this.props.name}</div>
    )
  }
}
 
3.子传父用函数
这里的方法和vue中类似(vue还可以用组件自定义事件)
 1、在父组件中通过属性给子组件传递一个回调
class App extends React.Component {
    getData(data) {
        console.log(data);
    }
    render() {
        return (
            <div>
                <Son getSonData={(data) => this.getData(data)}/>
            </div>
        )
    }
}
 
2、子组件可以调用父组件传的函数,并把组件数据作为参数传递过去,然后父组件就能拿到子组件的数据并进行后续操作。
class Son extends Component {
  constructor(props) {
    super(props);
    this.state = {
      sonData: '我是子组件数据'
    }
  }
  sendData() {
    //调用父组件传的函数并把子组件数据作为参数传过去
    this.props.getSonData(this.state.sonData);
  }
  render() {
    return (
      <div>
        <h2>子组件</h2>
        <button onClick={this.sendData.bind(this)}>点击把子组件数据传给父组件</button>
      </div>
    )
  }
}
 
4.案例练习
给我实现下面这个效果:
 
 这里我们把上面的导航栏封装成组件Navigate ,整体的思路如下:
 1、父组件存放数据,先传给子组件一份
 2、子组件接收数据并遍历展示
 3、子组件添加按钮,动态显示类名active(原理就是通过点击事件修改currentIndex)
 4、父组件给子组件一个回调,子组件动态显示类名后,把当前index传给父组件
 5、父组件接收index并存起来,然后在下面展示对应的数据。
class App extends React.Component {
    constructor() {
        super();
        this.state = {
            navList: ['新款', '精选', '流行'],
            contentIndex: 0,
        }
    }
    getContent(index) {
        this.setState({
            contentIndex: index
        })
    }
    render() {
        let { navList, index } = this.state;
        return (
            <div>
                <Navigation navList={navList} getContent={(index) => this.getContent(index)} />
                <h2>{navList[contentIndex]}</h2>
            </div>
        )
    }
}
 
export class Navigate extends Component {
  constructor(props) {
    super(props);
    this.state = {
      currentIndex: 0,
    }
  }
  changeIndex(index) {
    this.setState({
      currentIndex: index
    })
    this.props.getContent(index);
  }
  render() {
    let { currentIndex } = this.state;
    let { navList } = this.props
    return (
      <div className='nav'>
        {navList.map((nav, index) => {
          return (
            <div
              key={nav}
              className={`title ${currentIndex === index ? 'active' : ''}`}
              onClick={() => this.changeIndex(index)}
            >
              {nav}
            </div>
          )
        })}
      </div>
    )
  }
}
 
备注:react中使用scss:npm add node-sass@npm:dart-sass
// 安装scss:npm add node-sass@npm:dart-sass
.nav {
    border: 2px solid black;
    display: flex;
    justify-content: space-around;
    .title {
        padding: 10px;
        &.active {
            color: red;
            border-bottom: 3px solid red;
        }
    }
}
 
六、React插槽效果
1.通过props.children传递
我们在父组件中的子组件标签内部写几个div,那么子组件中就可以通过this.props.children读取到我们写的这些div,如果写多个,那么children是一个数组,如果写一个,那么children就是一个react元素(当然啊,我们可以通过propType限制children的类型)。这样的话,我们就可以拿着这些东西去子组件展示
2.通过props直接传递
比第一种更好的方式,就是我们在父组件中的子组件标签上直接添加属性,传入相应的react元素,子组件就可以通过props直接读取,直接用,非常奈斯
3.作用域插槽
本质上还是父给子传个函数,然后子去调用并把当前的数据传给父组件,父组件根据数据的类型,返回不同的节点。



















