React基础篇(一)

411 阅读18分钟

React基础.png

注:!!!

由于内容较多,分2部分,后半部分:React基础篇(二)

一、React入门

1.React简介

  • Facebook开源的一个js库

  • 一个用来动态构建用户界面的js库(只关注于视图(View))

2.React的特点

  • 声明式编码

  • 组件化编码

  • 一次学习,随处编写(React Native 编写原生应用、服务器端渲染)

  • 高效(优秀的Diffing算法, 编码人员不直接操作DOM)

  • 单向数据流

3.React高效的原因

  • 使用虚拟(virtual)DOM, 不总是直接操作页面真实DOM。(减少更新的次数)

  • 高效的DOM Diffing算法, 最小化页面重绘。(减小页面更新的区域)

4.React的初体验

1). 导入相关js库文件(react.development.js, react-dom.development.js, babel.min.js)

  • 引入react核心库

  • 引入react-dom, 用于支持react操作DOM

  • 引入babel, 用于将jsx转为js

2). 创建虚拟DOM,然后渲染虚拟DOM到页面

1). 导入相关js库文件(react.development.js, react-dom.development.js, babel.min.js)
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom, 用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel, 用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>

2). 创建虚拟DOM,然后渲染虚拟DOM到页面:
<!-- 准备好一个“容器” -->
<div id="test"></div>
<script type="text/babel"> /* 此处一定要写babel */
	//1.创建虚拟DOM
	const VDOM = <h1>Hello,React</h1> /* 此处一定不要写引号,因为不是字符串 */
	//2.渲染虚拟DOM到页面
	ReactDOM.render(VDOM, document.getElementById('test')); //该句话不是追加,是替换
</script>
  • 注: 此时只是测试语法使用, 并不是真实项目开发使用!

5.虚拟DOM的两种创建方式

1.纯JS方式(一般不用)

React.createElement('h1',{id:'title'}, React.createElement('span',{},'Hello,React'))

2.JSX方式

const VDOM = (  /* 此处一定不要写引号,因为不是字符串 */
    <h1 id="title">
        <span>Hello,React</span>
    </h1>
)

实例:

1.JS方式(一般不用)
<!-- 准备好一个“容器” -->
<div id="test"></div>
<script type="text/javascript" > 
    //1.创建虚拟DOM
    const VDOM = React.createElement('h1',{id:'title'}, React.createElement('span',{},'Hello,React'))
    //2.渲染虚拟DOM到页面
    ReactDOM.render(VDOM,document.getElementById('test'))
</script>

2.JSX方式
<!-- 准备好一个“容器” -->
<div id="test"></div>
<script type="text/babel" > /* 此处一定要写babel */
	//1.创建虚拟DOM
	const VDOM = (  /* 此处一定不要写引号,因为不是字符串 */
		<h1 id="title">
			<span>Hello,React</span>
    	</h1>
	)
	//2.渲染虚拟DOM到页面
	ReactDOM.render(VDOM,document.getElementById('test'))
</script>

6.虚拟DOM与真实DOM

关于虚拟DOM:

1.本质是Object类型的对象(一般对象),React源码里称 ReactElement 对象。

2.虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在用,无需真实DOM上那么多的属性。

3.虚拟DOM最终会被React转化为真实DOM,呈现在页面上。

实例:

<div id="test"></div>
<div id="demo"></div>
<script type="text/babel" > /* 此处一定要写babel */
    //1.创建虚拟DOM
    const VDOM = (  /* 此处一定不要写引号,因为不是字符串 */
        <h1 id="title">
            <span>Hello,React</span>
        </h1>
    )
    //2.渲染虚拟DOM到页面
    ReactDOM.render(VDOM,document.getElementById('test'));
    //真实DOM
    const TDOM = document.getElementById('demo');
    console.log('虚拟DOM',VDOM);
    console.log('真实DOM',TDOM);
    // debugger;
    console.log(typeof VDOM);  //object
    console.log(VDOM instanceof Object);  //true
</script>

7.JSX的理解和使用

1) 理解

​ 1.全称: JavaScript XML

​ 2.react定义的一种类似于XML的JS扩展语法: JS + XML

​ 3.JSX本质 是React. createElement (component, props, ...children)方法的 语法糖

​ 4.作用: 用来简化创建虚拟DOM

​ 1) 写法:

var ele = <h1>Hello JSX!</h1>

​ 2) 注1: 它不是字符串, 也不是HTML/XML标签

​ 3) 注2: 它最终产生的就是一个JS对象(ReactElement对象)

2) jsx语法规则:

​ 1.定义虚拟DOM时,不要写引号。

​ 2.JSX中嵌入 变量 时要用{}。

  • 当变量是Number、String、Array类型时,可以直接显示,数组会自动展开所有成员。
  • 当变量是null、undefined、Boolean类型时,内容为空。
    • 如果希望可以显示null、undefined、Boolean,那么需要转成字符串。
    • 转换的方式有很多,比如toString方法、和空字符串拼接,String(变量)等方式。
  • 对象类型不能作为React子元素放在标签体中直接显示。
<div id="app"></div>
<script type="text/babel">
    class App extends React.Component {
      constructor(props) {
        super(props);

        this.state = {
          // 1.在{}中可以正常显示显示的内容
          name: "why", // String
          age: 18, // Number
          names: ["abc", "cba", "nba"], // Array

          // 2.在{}中不能显示(忽略)
          test1: null, // null
          test2: undefined, // undefined
          test3: true, // Boolean
          flag: true,

          // 3.对象不能作为jsx的子类
          friend: {
            name: "kobe",
            age: 40
          }
        }
      }

      render() {
        return (
          <div>
            <h2>{this.state.name}</h2>
            <h2>{this.state.age}</h2>
            <h2>{this.state.names}</h2>

            <h2>{this.state.test1 + ""}</h2>
            <h2>{this.state.test2 + ""}</h2>
            <h2>{this.state.test3.toString()}</h2>

            <h2>{this.state.flag ? "你好啊": null}</h2>

            <h2>{this.state.friend}</h2>
          </div>
        )
      }
    }

    ReactDOM.render(<App/>, document.getElementById("app"));
</script>

​ 3.JSX中嵌入 表达式 时要用{}。

  • 运算表达式

  • 三元运算符

  • 执行一个函数

<div id="app"></div>
<script type="text/babel">
    class App extends React.Component {
      constructor(props) {
        super(props);

        this.state = {
          firstname: "kobe",
          lastname: "bryant",
          isLogin: false
        }
      }

      render() {
        const { firstname, lastname, isLogin } = this.state;

        return (
          <div>
            {/*1.运算符表达式*/}
            <h2>{ firstname + " " + lastname }</h2>
            <h2>{20 * 50}</h2>

            {/*2.三元表达式*/}
            <h2>{ isLogin ? "欢迎回来~": "请先登录~" }</h2>

            {/*3.进行函数调用*/}
            <h2>{this.getFullName()}</h2>
          </div>
        )
      }

      getFullName() {
        return this.state.firstname + " " + this.state.lastname;
      }
    }

    ReactDOM.render(<App/>, document.getElementById("app"));
</script>

​ 4.jsx绑定元素属性

  • 比如元素都会有title属性

  • 比如img元素会有src属性

  • 比如a元素会有href属性

  • 比如元素可能需要绑定class

  • 比如原生使用内联样式style

  • 注:

    • 样式的类名指定不要用class,要用className。

    • 内联样式,要用 style={{key: value}} 的形式去写。两个单词的css样式要写 小驼峰 的形式。 将样式以key: value的形式,放在对象里, 最后放入{}中。React 会在指定元素数字后自动添加 px。

<div id="app"></div>
<script type="text/babel">
    function getSizeImage(imgUrl, size) {
      return imgUrl + `?param=${size}x${size}`
    }

    class App extends React.Component {
      constructor(props) {
        super(props);

        this.state = {
          title: "标题",
          imgUrl: "http://p2.music.126.net/L8IDEWMk_6vyT0asSkPgXw==/109951163990535633.jpg",
          link: "http://www.baidu.com",
          active: true
        }
      }

      render() {
        const { title, imgUrl, link, active } = this.state;
        return (
          <div>
            {/* 1.绑定普通属性 */}
            <h2 title={title}>我是标题</h2>
            <img src={getSizeImage(imgUrl, 140)} alt=""/>
            <a href={link} target="_blank">百度一下</a>

            {/* 2.绑定class */}
            <div className="box title">我是div元素</div>
            {/* 动态绑定class */}
            <div className={"box title " + (active ? "active": "")}>我也是div元素</div>
            <label htmlFor=""></label>

            {/* 3.绑定style */}
            <div style={{color: "red", fontSize: "50px"}}>我是div,绑定style属性</div>
          </div>
        )
      }
    }

    ReactDOM.render(<App />, document.getElementById("app"));
</script>

​ 5.JSX的顶层只能有一个根元素(模板),所以很多时候会在外层包裹一个div原生(或者使用Fragment)。

​ 6.标签必须闭合,如果是单标签,必须以/>结尾。

​ 7.jsx标签中的注释:

{/*注释...*/}

​ 8.jsx标签外的注释:

/*注释...*/

​ 9.标签首字母

​ (1).若小写字母开头,则将该标签转为html中同名元素,若html中无该标签对应的同名元素,则报错。

​ (2).若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。

​ 10.每个列表的li都应该有唯一的key, key应该设置在兄弟节点,且只是在兄弟节点之间必须唯一。

注:

  • 一定注意区分:【js语句(代码)】与【js表达式】

​ 1.表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方

​ 下面这些都是表达式:

​ (1). a

​ (2). a+b

​ (3). demo(1)

​ (4). arr.map() ===> 用于加工数组

​ (5). function test() {}

​ 2.语句(代码)

​ 下面这些都是语句:

​ (1). if(){}

​ (2). for(){}

​ (3). switch(){case:xxx}

实例:

<!-- 准备好一个“容器” -->
<div id="test"></div>
<script type="text/babel">
    //准备一些数据
    const myId = 'aTgUiGu'
    const myData = 'HeLlo,rEaCt'

    //1.创建虚拟DOM
    const VDOM = (
        <div>
            <h2 className="title" id={myId.toLowerCase()}>
                <span style={{color:'white',fontSize:'29px'}}>{myData.toLowerCase()}</span>
            </h2>
            <h2 className="title" id={myId.toUpperCase()}>
                <span style={{color:'white',fontSize:'29px'}}>{myData.toLowerCase()}</span>
            </h2>
            <input type="text"/>
            <peiqi>123</peiqi>
        </div>
    )
    //2.渲染虚拟DOM到页面, 即虚拟DOM => 真实DOM,呈现在页面
    ReactDOM.render(VDOM,document.getElementById('test'))
</script>

jsx练习-动态展示列表

<div id="test"></div>
<script type="text/babel">	
	//模拟后台返回的数据
	const arr = ['Angular','React','Vue']

	//1.创建虚拟DOM
	const VDOM = (
		<div>
			<h1>前端框架列表</h1>	
			<ul>
				{
					//map方法专门用于加工数组,React中大量使用map方法
					arr.map((item,index)=>{
						return <li key={index}>{item}</li>
					})
	
					//不能使用forEach,因为返回值永远是undefined
					/* arr.forEach((item,index)=>{
						return <li key={index}>{item}</li> //这么写是错误的
					}) */
	             }
			</ul>
		</div>
	);
	
	//2.渲染虚拟DOM到页面
	ReactDOM.render(VDOM,document.getElementById('test'))
</script>

8.模块与组件、模块化与组件化的理解

1). 模块与组件

1.模块:

  • 理解: 向外提供特定功能的js程序, 一般就是一个js文件

  • 为什么要拆成模块: 随着业务逻辑增加,js代码更多更复杂

  • 作用: 复用js, 简化js的编写, 提高js运行效率

2.组件:

  • 理解: 用来实现特定(局部)界面功能效果的代码集合(html/css/js/img)

  • 为什么要用组件: 一个界面的功能太复杂了

  • 作用: 复用编码, 简化项目界面编码, 提高运行效率

2). 模块化与组件化

1.模块化:

  • 当应用的js都以模块来编写的, 这个应用就是一个模块化的应用

2.组件化:

  • 当应用是以多组件的方式实现, 这个应用就是一个组件化的应用

9.命令式编程与声明式编程

1.声明式编程

  • 只关注做什么,而不关注怎么做(流程),类似于填空题。每做一个操作,都是给计算机(浏览器)一步步命令。

2.命令式编程

  • 要关注做什么和怎么做(流程),类似于问答题

3.虚拟DOM帮助我们从命令式编程转到了声明式编程的模式

4.声明式编程的方式赋予了React声明式的API:

  • 你只需要告诉React希望让UI是什么状态;

  • React来确保DOM和这些状态是匹配的;

  • 你不需要直接进行DOM操作,只可以从手动更改DOM、属性操作、事件处理中解放出来;

需求:

原数组:[1, 3, 5, 7],得到一个新的数组, 数组中每个元素都比原数组中对应的元素大10: [11, 13, 15, 17]

var arr = [1, 3, 5, 7];
// 1-命令式编程
var arr2 = [];
for(var i = 0;i<arr.length;i++) {
	arr2.push(arr[i] + 10);
}
console.log(arr2);

// 2-声明式编程
var arr3 = arr.map(function(item){
	return item + 10;
})

注:

  • 声明式编程是建立命令式编程的基础上

  • 数组中常见声明式方法:map() / filter() / reduce() / forEach() / find() / findIndex()

二、React组件化开发

1.组件的创建与使用

1). 组件分类: 函数式组件 / 类式组件

2). 创建组件(标签)的方式

方式1: 函数式组件(简单组件/无状态组件)

function MyComponent1(props) {
    return <h1>自定义组件标题11111</h1>;
}

注:

  • 没有生命周期,也会被更新并挂载,但是没有生命周期函数
  • 没有this(组件实例)
  • 没有内部状态(state)

方式2: 类式组件(复杂组件/有状态组件, 推荐使用!)

class MyComponent2 extends React.Component {
    render () {
        return <h1>自定义组件标题33333</h1>;
    }
}

3). 渲染组件(标签)

ReactDOM.render(<MyComponent1 />,  cotainerElement);

4). ReactDOM.render()渲染函数式组件(标签)的基本流程

​ 1.React解析组件标签,寻找MyComponent组件:

​ (1).若找到了,则进行下一步

​ (2).若未找到,则报错:MyComponent is not defined

​ 2.React发现MyComponent是用函数定义的,随后React调用该函数,将返回的虚拟DOM转为真实DOM,渲染到页面上。

5). ReactDOM.render()渲染类式组件(标签)的基本流程

​ 1.React解析组件标签,寻找MyComponent组件

​ (1).若找到了,则进行下一步

​ (2).若未找到,则报错:MyComponent is not defined

​ 2.React发现组件是使用类定义的,随后React帮我们去new了一个MyComponent的实例:mc, ​ 并通过mc调用到了MyComponent原型上的render方法,即:mc.render()

​ 备注:此处的mc只是我们分析问题时的一个代号,React底层用的是别的名字。

​ 3.将render返回的虚拟DOM转为真实DOM,随后渲染到页面上。

6). render函数的返回值

  • 当 render 被调用时,它会检查 this.props 和 this.state 的变化并返回以下类型之一:
  • React 元素:
    • 通常通过 JSX 创建。
    • 例如,< div / > 会被 React 渲染为 DOM 节点,< MyComponent / > 会被 React 渲染为自定义组件。
    • 无论是 < div / > 还是 < MyComponent / > 均为 React 元素。
  • 数组 或 fragments:使得 render 方法可以返回多个元素。
  • Portals:可以渲染子节点到不同的 DOM 子树中。
  • 字符串 或 数值类型:它们在 DOM 中会被渲染为文本节点。
  • 布尔类型 或 null:什么都不渲染。

注:

​ 1.组件名必须首字母大写

​ 2.所谓的简单组件,就是没有状态的组件

​ 3.所谓的复杂组件,就是有状态的组件

实例:

1.创建函数式组件

<div id="test"></div>
<script type="text/babel">
    //1.创建函数式组件
    function MyComponent (){
        console.log(this)  //此处的this是 undefined, 因为jsx编译为js后babel开启了严格模式
        return <h2>我是用函数定义的组件(适用于:【简单组件】的定义)</h2>
    }
    //2.渲染组件到页面
    ReactDOM.render(<MyComponent/>,document.getElementById('test'));
</script>

2.创建类式组件

<div id="test"></div>
<script type="text/babel">
    //1.创建类式组件
    class MyComponent extends React.Component {
        render(){
            //render是放在哪里的?—— MyComponent的原型对象上,供实例使用。
            //render中的this是谁?—— MyComponent类的实例对象 <=> MyComponent组件实例对象。
            console.log('render中的this:', this);
            return <h2>我是用类定义的组件(适用于:【复杂组件】的定义)</h2>;
        }
    }
    //2.渲染组件到页面
    ReactDOM.render(<MyComponent/>,document.getElementById('test'))
</script>

2.组件实例的3大属性1: state

1.state是组件实例对象最重要的属性, 值是对象(可以包含多个key-value的组合)

2.组件被称为"状态机", 通过更新组件的state来更新对应的页面显示(重新渲染组件)

2.构造函数初始化指定:

constructor(props) {
    super(props);
    this.state = {
        stateName1 : stateValue1,
        stateName2 : stateValue2
    };
}

4.class里直接初始化指定( 简写方式 - 推荐! ):

class Weather extends React.Component{
    state = {
        isHot:true,
        wind:'微风'
    }
    ...
}

5.读取显示:

​ this.state.stateName

6.更新状态 --> 更新界面:

this.setState({
    stateProp1 : value1,
    stateProp2 : value2
});

7.注意

​ 1.组件中render方法中的this为组件实例对象

​ 2.状态数据,不能直接修改或更新,必须调用setState()方法修改或更新

实例:

需求: 定义一个展示天气信息的组件

1.默认展示天气炎热 或 凉爽

2.点击文字切换天气

<div id="test"></div>
<script type="text/babel">
    //1.创建组件
    class Weather extends React.Component{
        //构造器调用几次? ———— 1次
        constructor(props){
            console.log('constructor');
            super(props);
            //初始化状态
            this.state = {isHot:false,wind:'微风'};
            //(写法1)-用bind解决changeWeather中this指向问题
            //用改变原型上changeWeather中this指向,返回的新函数添加给实例
            //官方说法-为事件处理函数绑定实例
            // this.changeWeather = this.changeWeather.bind(this);
        }

        //render调用几次? ———— 1 + n次,1是初始化的那次 n是状态更新的次数
        render(){
            console.log('render');
            //读取状态
            const {isHot,wind} = this.state;
            //(写法2)-直接在事件绑定处用bind解决changeWeather中this指向问题,节省了在构造函数里再写代码给实例本身加changeWeather方法
            return <h1 onClick = {this.changeWeather.bind(this)}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>;
}

        //changeWeather调用几次? ———— 点几次调几次
        changeWeather(){
            //changeWeather放在哪里? ———— Weather的原型对象上,供实例使用
            //由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
            //类中的方法默认开启了局部的严格模式,以及babel编译后全局开启了严格模式,所以changeWeather中的this为undefined
    
            console.log('changeWeather');
            //获取原来的isHot值
            const isHot = this.state.isHot;
            //严重注意:状态必须通过setState进行更新,且更新是一种合并,不是替换。
            this.setState({isHot:!isHot});  //setState干了两件事:1.帮你更改状态 2.重新调用render
            console.log(this);

            //严重注意:状态(state)不可直接更改,下面这行就是直接更改!!!
            //this.state.isHot = !isHot //这是错误的写法
        }
    }
    //2.渲染组件到页面
    ReactDOM.render(<Weather/>,document.getElementById('test'));
</script>

1) state的简写方式

<div id="test"></div>
<script type="text/babel">
    //1.创建组件
    class Weather extends React.Component{
        //初始化状态
        state = {isHot:false,wind:'微风'};

        render(){
            const {isHot,wind} = this.state;
            return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>;
        }
    
        //自定义方法-类组件中事件的回调———要用赋值语句的形式+箭头函数
        changeWeather = ()=>{
            const isHot = this.state.isHot;
            this.setState({isHot:!isHot});
            this.haha();
        }
        //haha不作为事件的回调使用,那么haha就没有必要:赋值语句+箭头函数去写
        haha(){
            console.log('哈哈');
        }
    }
    //2.渲染组件到页面
    ReactDOM.render(<Weather/>,document.getElementById('test'));
</script>

2) 扩展实例-天气3状态切换

<div id="test"></div>
<script type="text/babel" >
    //1.定义组件
    class Weather extends React.Component{
        //初始化状态
        state ={
            temp: 1  //1炎热 2温暖 3凉爽
        }
        render(){
            const {temp} = this.state
            return (
                <div>
                    <h2>
                        今天天气很{
                        temp === 1 ? '炎热' :
                        temp === 2 ? '温暖' :
                        temp === 3 ? '凉爽' : '未知'
                        }
                    </h2>
                    <button onClick={this.changeWeather}>点我切换天气</button>
                </div>
            )
        }
        changeWeather = ()=>{
            //获取原状态
            let {temp}  = this.state
            //自增
            temp += 1
            //校验数据
            if(temp>3) temp = 1
            //更新状态
            this.setState({temp})
        }
    }
    //2.渲染组件
    ReactDOM.render(<Weather/>,document.getElementById('test'))
</script>

3.组件实例的3大属性2: props

1.每个组件实例对象都会有props(properties的简写)属性

2.组件标签的所有属性都保存在props中

3.在组件内部读取属性: this.props.propertyName

4.props是只读的

5.作用:

​ 1) 通过标签属性从组件外向组件内传递变化的数据(父传子)

​ 2) 注意: 组件内部不要修改props数据

6.对props中的属性值进行类型限制和必要性限制

​ 1) 使用prop-types库进限制(需要引入prop-types库)

Person.propTypes = {
    name: PropTypes.string.isRequired, //限制name必传,且为字符串
    age: PropTypes.number, //限制age为数值
    speak:PropTypes.func, //限制speak为函数
}
  • isRequired为必传属性

​ 2) 简写方式

//对标签属性进行类型、必要性的限制
static propTypes = {
    name:PropTypes.string.isRequired, //限制name必传,且为字符串
    sex:PropTypes.string, //限制sex为字符串
    age:PropTypes.number, //限制age为数值
}

7.属性展开 (组件传参简写)- 将对象的所有属性通过props传递,即批量传递props(标签属性)

//在react和babel作用下,对象可以用...拆对象,但只能用于组件传参!
<Person {...person}/>

8.默认属性值

Person.defaultProps = {
    sex:'男', //sex默认值为男
    age:18 //age默认值为18
}

简写方式:

static defaultProps = {
    sex:'男', //sex默认值为男
    age:18 //age默认值为18
}

9.类式组件的构造函数接收props

constructor(props){
    super(props);
    console.log(props); //打印所有属性
}

注: 可以不写构造函数,源码默认给实例对象加上了props,即可以直接使用this.props.xxx获取。

10.函数式组件使用props

  • 函数式组件只能通过传参使用props属性
<div id="test"></div>
<script type="text/babel">
    //创建组件
    //函数式组件只能通过传参使用props属性
    function Person (props){
        const {name,age,sex} = props
        return (
            <ul>
                <li>姓名:{name}</li>
                <li>性别:{sex}</li>
                <li>年龄:{age}</li>
        	</ul>
    	)
    }
    Person.propTypes = {
        name:PropTypes.string.isRequired, //限制name必传,且为字符串
        sex:PropTypes.string, //限制sex为字符串
        age:PropTypes.number, //限制age为数值
    }
    //指定默认标签属性值
    Person.defaultProps = {
        sex:'男', //sex默认值为男
        age:18 //age默认值为18
    }
    //渲染组件到页面
    ReactDOM.render(<Person name="jerry"/>,document.getElementById('test'))
</script>

实例:

需求: 自定义用来显示一个人员信息的组件

1)姓名必须指定,且为字符串类型

2)性别为字符串类型,如果性别没有指定,默认为男

3)年龄为字符串类型,且为数字类型,默认值为18

//1.props基本使用
<div id="test1"></div>
<div id="test2"></div>
<div id="test3"></div>
<script type="text/babel">
    //创建组件
    class Person extends React.Component{
        render(){
            // console.log(this);
            const {name,age,sex} = this.props
            return (
                <ul>
                    <li>姓名:{name}</li>
                    <li>性别:{sex}</li>
                    <li>年龄:{age+1}</li>
                </ul>
			)
    	}
    }
    //渲染组件到页面
    ReactDOM.render(<Person name="jerry" age={19}  sex="男"/>,document.getElementById('test1'))
    ReactDOM.render(<Person name="tom" age={18} sex="女"/>,document.getElementById('test2'))

    //模拟一个人的信息(真实项目开发中,一定是从服务器获取到的)
    const p = {name:'老刘',age:18,sex:'女'}
    // console.log('@',...p); //此处的...p不奏效
    // ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>,document.getElementById('test3'))
    
    //组件传参简写-即批量传递props(标签属性)
    //在react库和babel作用下可以对象可以用...拆对象,但只能用于组件传参!
    ReactDOM.render(<Person {...p}/>,document.getElementById('test3'))
</script>


//2.对props进行限制
<div id="test1"></div>
<div id="test2"></div>
<div id="test3"></div>
<script type="text/babel">
	//创建组件
    class Person extends React.Component{
    	render(){
            // console.log(this);
            const {name,age,sex} = this.props
            //props是只读的
            //this.props.name = 'jack' //此行代码会报错,因为props是只读的
            return (
                <ul>
                    <li>姓名:{name}</li>
                    <li>性别:{sex}</li>
                    <li>年龄:{age+1}</li>
                </ul>
    		)
    	}
    }
    //对标签属性进行类型、必要性的限制
    Person.propTypes = {
        name:PropTypes.string.isRequired, //限制name必传,且为字符串
        sex:PropTypes.string, //限制sex为字符串
        age:PropTypes.number, //限制age为数值
        //限制函数用func
        speak:PropTypes.func, //限制speak为函数
    }
    //指定默认标签属性值
    Person.defaultProps = {
        sex:'男', //sex默认值为男
        age:18 //age默认值为18
    }
	//渲染组件到页面
    ReactDOM.render(<Person name="jack" speak={speak}/>,document.getElementById('test1'))
    ReactDOM.render(<Person name="tom" age={18} sex="女"/>,document.getElementById('test2'))

    const p = {name:'老刘',age:18,sex:'女'}
    // console.log('@',...p);
    // ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>,document.getElementById('test3'))
    ReactDOM.render(<Person {...p}/>,document.getElementById('test3'))
    
    function speak(){
        console.log('我说话了');
    }
</script>


//3.props的简写方式
<div id="test1"></div>
<script type="text/babel">
	//创建组件
    class Person extends React.Component{
        //构造器一般能省则省
        constructor(props){
            //注意:构造器是否接收props,是否传递给super,取决于:是否希望在构造器中通过this访问props
            // console.log(props);
            super(props);
            //这种写法很少
            console.log('constructor',this.props);
        }

        //对标签属性进行类型、必要性的限制
        static propTypes = {
            name:PropTypes.string.isRequired, //限制name必传,且为字符串
            sex:PropTypes.string, //限制sex为字符串
            age:PropTypes.number, //限制age为数值
        }
    
        //指定默认标签属性值
        static defaultProps = {
            sex:'男', //sex默认值为男
            age:18 //age默认值为18
        }
    
        render(){
            // console.log(this);
            const {name,age,sex} = this.props
            //props是只读的
            //this.props.name = 'jack' //此行代码会报错,因为props是只读的
            return (
                <ul>
                    <li>姓名:{name}</li>
                    <li>性别:{sex}</li>
                    <li>年龄:{age+1}</li>
                </ul>
            )
    	}
    }
    
    //渲染组件到页面
    ReactDOM.render(<Person name="jerry"/>,document.getElementById('test1'))
</script>

11.子组件传父组件数据 - 通过传递函数属性的props实现

  • 某些情况,我们也需要子组件向父组件传递消息:
    • 在vue中是通过自定义事件来完成的;
    • 在React中同样是通过props传递消息,只是让父组件给子组件传递一个回调函数,在子组件中调用这个函数即可;

实例:

import React, { Component } from 'react';

class CounterButton extends Component {
  render() {
    const {onClick} = this.props;
    return <button onClick={onClick}>+1</button>
  }
}

export default class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      counter: 0
    }
  }

  render() {
    return (
      <div>
        <h2>当前计数: {this.state.counter}</h2>
        <button onClick={e => this.increment()}>+</button>
        {/* 父传给子函数,子调用,即可实现子传父 */}
        <CounterButton onClick={e => this.increment()} name="why"/>
      </div>
    )
  }

  increment() {
    this.setState({
      counter: this.state.counter + 1
    })
  }
}

父子通信案例:tab切换

//TabControl.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';

export default class TabControl extends Component {
  constructor(props) {
    super(props);

    this.state = {
      currentIndex: 0
    }
  }

  render() {
    const { titles } = this.props;
    const {currentIndex} = this.state;

    return (
      <div className="tab-control">
        {
          titles.map((item, index) => {
            return (
              <div key={item} 
                   className={"tab-item " + (index === currentIndex ? "active": "")}
                   onClick={e => this.itemClick(index)}>
                <span>{item}</span>
              </div>
            )
          })
        }
      </div>
    )
  }

  itemClick(index) {
    this.setState({
      currentIndex: index
    })

    const {itemClick} = this.props;
    itemClick(index);
  }
}

TabControl.propTypes = {
  titles: PropTypes.array.isRequired
}


//app.js
import React, { Component } from 'react';
import TabControl from './TabControl';

export default class App extends Component {
  constructor(props) {
    super(props);

    this.titles = ['新款', '精选', '流行'];

    this.state = {
      currentTitle: "新款",
      currentIndex: 0
    }
  }

  render() {
    const {currentTitle} = this.state;

    return (
      <div>
        <TabControl itemClick={index => this.itemClick(index)} titles={this.titles} />
        <h2>{currentTitle}</h2>
      </div>
    )
  }

  itemClick(index) {
    this.setState({
      currentTitle: this.titles[index]
    })
  }
}

注意:css类的动态绑定-字符串拼接!

12.children props 与 jsx props - 传入结构(标签)

1)children props:

  • 传入 - 多个则以数组保存
<NavBar name="" title="" className="">
    <span>aaa</span>
    <strong>bbb</strong>
    <a href="/#">ccc</a>
</NavBar>
  • 读取 - 多个以数组下标读取
<div className="nav-left">
    {this.props.children[0]}
</div>
<div className="nav-item nav-center">
    {this.props.children[1]}
</div>
<div className="nav-item nav-right">
    {this.props.children[2]}
</div>

2)jsx props (推荐!)

  • 传入 - 多个保存到对象
<NavBar2 leftSlot={<span>aaa</span>}
         centerSlot={<strong>bbb</strong>}
         rightSlot={<a href="/#">ccc</a>}/>
  • 读取 - 多个以key获取
<div className="nav-left">
    {this.props.leftSlot}
</div>
<div className="nav-item nav-center">
    {this.props.centerSlot}
</div>
<div className="nav-item nav-right">
    {this.props.rightSlot}
</div>

注:

  • react 实现 vue 的插槽
  • 缺点:不能携带数据

案例:app顶部导航条

//app.js
import React, { Component } from 'react';
import NavBar from './NavBar';
import NavBar2 from './NavBar2';

export default class App extends Component {

  render() {
    const leftJsx = <span>aaa</span>;
    return (
      <div>
        <NavBar name="" title="" className="">
          <span>aaa</span>
          <strong>bbb</strong>
          <a href="/#">ccc</a>
        </NavBar>

        <NavBar2 leftSlot={leftJsx}
                 centerSlot={<strong>bbb</strong>}
                 rightSlot={<a href="/#">ccc</a>}/>
      </div>
    )
  }
}

//NavBar.js
import React, { Component } from 'react'

export default class NavBar extends Component {
  render() {
    // this.props.children;
    return (
      <div className="nav-item nav-bar">
        <div className="nav-left">
          {this.props.children[0]}
        </div>
        <div className="nav-item nav-center">
          {this.props.children[1]}
        </div>
        <div className="nav-item nav-right">
          {this.props.children[2]}
        </div>
      </div>
    )
  }
}

//NavBar2.js
import React, { Component } from 'react'

export default class NavBar2 extends Component {
  render() {
    const {leftSlot, centerSlot, rightSlot} = this.props;

    return (
      <div className="nav-item nav-bar">
        <div className="nav-left">
          {leftSlot}
        </div>
        <div className="nav-item nav-center">
          {centerSlot}
        </div>
        <div className="nav-item nav-right">
          {rightSlot}
        </div>
      </div>
    )
  }
}

13.render props

  • 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性
<A render={(data) => <C data={data}></C>}></A>

A组件: {this.props.render(内部state数据)}

C组件: 读取A组件传入的数据显示 {this.props.data}

实例

import React, { Component } from 'react'
import './index.css'
import C from '../1_setState'

export default class Parent extends Component {
	render() {
		return (
			<div className="parent">
				<h3>我是Parent组件</h3>
				<A render={(name)=><C name={name}/>}/>
			</div>
		)
	}
}

class A extends Component {
	state = {name:'tom'}
	render() {
		console.log(this.props);
		const {name} = this.state
		return (
			<div className="a">
				<h3>我是A组件</h3>
				{this.props.render(name)}
			</div>
		)
	}
}

class C extends Component {
	render() {
		console.log('C--render');
		return (
			<div className="c">
				<h3>我是C组件,{this.props.name}</h3>
			</div>
		)
	}
}

4.组件实例的3大属性3: refs

1.组件内包含ref属性的标签元素的集合对象

2.组件内的标签可以定义ref属性来标识自己

3.作用: 找到组件内部的真实dom元素对象, 进而操作它

4.ref分类

​ 1) 字符串形式的ref (即将被弃用!)

<input ref="input1"/>
  • 在组件内部获得标签对象: this.refs.refName(只是得到了标签元素对象)

​ 2) 回调形式的ref

<input ref={(c) => {this.input1 = c}}/>
  • 在组件内部获得标签节点: this.input1

​ 3) createRef创建ref容器 (推荐!)

myRef = React.createRef()
<input ref={this.myRef}/>
  • 在组件内部获得标签节点: this.myRef.current

实例:

需求: 自定义组件, 功能说明如下:

1.点击按钮, 提示第一个输入框中的值

2.当第2个输入框失去焦点时, 提示这个输入框中的值

//1.字符串形式的ref(即将被弃用!)
<div id="test"></div>
<script type="text/babel">
	//字符串形式的ref有效率问题,即将被弃用!
    //创建组件
    class Demo extends React.Component{
        //展示左侧输入框的数据
        showData = ()=>{
            //refs通过ref收集的是真实的DOM!
            const {input1} = this.refs;
            alert(input1.value);
        }
        //展示右侧输入框的数据
        showData2 = ()=>{
            const {input2} = this.refs;
            alert(input2.value);
        }
        render(){
            return(
                <div>
                    <input ref="input1" type="text" placeholder="点击按钮提示数据"/>&nbsp;
                    <button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
                    <input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
    			</div>
			)
		}
	}
    //渲染组件到页面
    ReactDOM.render(<Demo a="1" b="2"/>,document.getElementById('test'))
</script>


//2.回调形式的ref
<div id="test"></div>
<script type="text/babel">
    //创建组件
    class Demo extends React.Component{
        //展示左侧输入框的数据
        showData = ()=>{
            const {input1} = this;
            alert(input1.value);
        }
        //展示右侧输入框的数据
        showData2 = ()=>{
            const {input2} = this;
            alert(input2.value);
        }
        render(){
            return(
                <div>
                	<input ref={c => this.input1 = c} type="text" placeholder="点击按钮提示数据"/>&nbsp;
                    <button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
                    <input onBlur={this.showData2} ref={c => this.input2 = c} type="text" placeholder="失去焦点提示数据"/>&nbsp;
				</div>
			)
		}
	}
    //渲染组件到页面
    ReactDOM.render(<Demo a="1" b="2"/>,document.getElementById('test'))
</script>


//3.回调ref中回调执行次数的问题
<div id="test"></div>
<script type="text/babel">
    //关于回调 refs 的说明
    //如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,
    //第一次传入参数 null,然后第二次会传入参数 DOM 元素。
    //这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。
    //通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。

    //创建组件
    class Demo extends React.Component{
        state = {isHot:false};
    
        showInfo = ()=>{
            const {input1} = this;
            alert(input1.value);
        }
    
        changeWeather = ()=>{
            //获取原来的状态
            const {isHot} = this.state;
            //更新状态
            this.setState({isHot:!isHot});
        }
    
        saveInput = (c)=>{
            this.input1 = c;
            console.log('@',c);
        }
    
        render(){
            const {isHot} = this.state;
            return(
                <div>
                    <h2>今天天气很{isHot ? '炎热':'凉爽'}</h2>
                    {/*<input ref={(c)=>{this.input1 = c;console.log('@',c);}} type="text"/><br/><br/>*/}
                    <input ref={this.saveInput} type="text"/><br/><br/>
                    <button onClick={this.showInfo}>点我提示输入的数据</button>
                    <button onClick={this.changeWeather}>点我切换天气</button>
        		</div>
        	)
        }
    }
    //渲染组件到页面
    ReactDOM.render(<Demo/>,document.getElementById('test'));
</script>


//4.createRef创建ref容器
<div id="test"></div>
<script type="text/babel">
    //创建组件
    class Demo extends React.Component{
        /*
			React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是“专人专用”的
		*/
        myRef = React.createRef();
        myRef2 = React.createRef();
        //展示左侧输入框的数据
        showData = ()=>{
            alert(this.myRef.current.value);
        }
        //展示右侧输入框的数据
        showData2 = ()=>{
            alert(this.myRef2.current.value);
        }
		render(){
    		return(
            	<div>
                    <input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>&nbsp;
                    <button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
                    <input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦点提示数据"/>&nbsp;
            	</div>
			)
		}
	}
    //渲染组件到页面
    ReactDOM.render(<Demo a="1" b="2"/>,document.getElementById('test'));
</script>

5.ref用于标识组件 - 子传父数据

  • 当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性
  • 父子组件间通信: 父组件需要子组件数据(给子组件设置ref,获取子组件数据)
  • 父组件得到子组件对象, 从而可以读取其状态数据或调用其方法更新其状态数据
  • 你不能在函数组件上使用 ref 属性,因为他们没有实例
  • 但是某些时候,我们可能想要获取函数式组件中的某个DOM元素
  • 这个时候我们可以通过 React.forwardRef ,后面也会学习 hooks 中如何使用ref

实例:

import React, { PureComponent, createRef, Component } from 'react';

class Counter extends PureComponent {
  constructor(props) {
    super(props);

    this.state = {
      counter: 0
    }
  }

  render() {
    return (
      <div>
        <h2>当前计数: {this.state.counter}</h2>
        <button onClick={e => this.increment()}>+1</button>
      </div>
    )
  }

  increment() {
    this.setState({
      counter: this.state.counter + 1
    })
  }
}

export default class App extends PureComponent {

  constructor(props) {
    super(props);

    this.titleRef = createRef();
    this.counterRef = createRef();
    this.titleEl = null;
  }

  render() {
    return (
      <div>
        {/* <h2 ref=字符串/对象/函数>Hello React</h2> */}
        <h2 ref="titleRef">Hello React</h2>
        {/* 目前React推荐的方式 */}
        <h2 ref={this.titleRef}>Hello React</h2>
        <h2 ref={arg => this.titleEl = arg}>Hello React</h2>
        <button onClick={e => this.changeText()}>改变文本</button>
        <hr/>
        <Counter ref={this.counterRef}/>
        <button onClick={e => this.appBtnClick()}>App按钮</button>
      </div>
    )
  }

  changeText() {
    // 1.使用方式一: 字符串(不推荐, 后续的更新会删除)
    this.refs.titleRef.innerHTML = "Hello Admin";
    // 2.使用方式二: 对象方式
    this.titleRef.current.innerHTML = "Hello JavaScript";
    // 3.使用方式三: 回调函数方式
    this.titleEl.innerHTML = "Hello TypeScript";
  }

  appBtnClick() {
    this.counterRef.current.increment();
  }
}

class App extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <div>
        <Counter title={}></Counter>
      </div>
    )
  }
}

5.事件处理+事件处理函数中this丢失问题

1.通过onXxx属性指定事件处理函数(注意大小写)

​ 1) React使用的是自定义(合成)事件, 而不是使用的原生DOM事件 —————— 为了更好的兼容性

​ 2) React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) ———————— 为了更高效

2.通过event.target得到发生事件的DOM元素对象 —————————— 不要过度使用ref

3.事件处理函数中this为undefined,如何解决?

​ a)使用bind()给事件处理函数显示绑定this

​ b)在组件中自定义事件处理函数时,使用箭头函数

​ c)(推荐!) 指定事件处理函数时使用箭头函数包括自己定义的函数,并在其内部调用自己定义的函数。

示例:

<div id="app"></div>
<script type="text/babel">
    class App extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          message: "你好啊",
          counter: 100
        }
        // 1.方案一(写法2): bind绑定this(显示绑定)
        // this.btnClick = this.btnClick.bind(this);
      }

      render() {
        return (
          <div>
            {/* 1.方案一(写法1): bind绑定this(显示绑定) */}
            <button onClick={this.btnClick.bind(this)}>按钮1</button>
            <button onClick={this.btnClick.bind(this)}>按钮2</button>
            <button onClick={this.btnClick.bind(this)}>按钮3</button>

            {/* 2.方案二: 定义函数时, 使用箭头函数 */}
            <button onClick={this.increment}>+1</button>

            {/* 2.方案三(推荐): 直接传入一个箭头函数, 在箭头函数中调用需要执行的函数*/}
            <button onClick={() => { this.decrement("why") }}>-1</button>
          </div>
        )
      }

      btnClick() {
        console.log(this.state.message);
      }

      // increment() {
      //   console.log(this.state.counter);
      // }

      // 箭头函数中永远不绑定this
      increment = () => {
        console.log(this.state.counter);
      }

      decrement(name) {
        console.log(this.state.counter, name);
      }
    }

    ReactDOM.render(<App/>, document.getElementById("app"));
</script>

4.事件处理函数传参

示例:

<div id="app"></div>
<script type="text/babel">
    class App extends React.Component {
      constructor(props) {
        super(props);

        this.state = {
          movies: ["大话西游", "海王", "流浪地球", "盗梦空间"]
        }

        this.btnClick = this.btnClick.bind(this);
      }

      render() {
        return (
          <div>
            <button onClick={this.btnClick}>按钮</button>
            
            <ul>
              {
                this.state.movies.map((item, index, arr) => {
                  return (
                    <li className="item" 
                        //使用此种定义事件处理函数的方法方便传参
                        onClick={ e => { this.liClick(item, index, e) }}
                        title="li">
                      {item}
                    </li>
                  )
                })
              }
            </ul>
          </div>
        )
      }

      btnClick(event) {
        console.log("按钮发生了点击", event);
      }

      liClick(item, index, event) {
        console.log("li发生了点击", item, index, event);
      }
    }

    ReactDOM.render(<App/>, document.getElementById("app"));
</script>

实例:

需求: 自定义组件, 功能说明如下:

1.点击按钮, 提示第一个输入框中的值

2.当第2个输入框失去焦点时, 提示这个输入框中的值

<div id="test"></div>
<script type="text/babel">
    //创建组件
    class Demo extends React.Component{
        //创建ref容器
        myRef = React.createRef();
        myRef2 = React.createRef();

        //点击按钮的回调-展示左侧输入框的数据
        showData = (event)=>{
            console.log(event.target);
            alert(this.myRef.current.value);
    	}
    
        //失去焦点的回调-展示右侧输入框的数据
        showData2 = (event)=>{
            //发生事件的元素与要操作的元素相同,可不使用ref
            alert(event.target.value);
    	}
    
    	render(){
    		return(
                <div>
                    <input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>&nbsp;
                    <button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
                    <input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>&nbsp;
                </div>
    		)
    	}
    }
    //渲染组件到页面
    ReactDOM.render(<Demo a="1" b="2"/>,document.getElementById('test'));
</script>

6.条件渲染

1.某些情况下,界面的内容会根据不同的情况显示不同的内容,或者决定是否渲染某部分内容:

  • 在vue中,我们会通过指令来控制:比如v-if、v-show
  • 在React中,所有的条件判断都和普通的JavaScript代码一致

2.常见的条件渲染方式

  • 方式一:条件判断语句
    • 适合逻辑较多的情况
  • 方式二:三元运算符
    • 适合逻辑比较简单
  • 与运算符&&
    • 适合如果条件成立,渲染某一个组件;如果条件不成立,什么内容也不渲染;
  • v-show的效果
    • 主要是控制display属性是否为none

实例:

<div id="app"></div>
<script type="text/babel">
    class App extends React.Component {
      constructor(props) {
        super(props);

        this.state = {
          isLogin: true
        }
      }

      render() {
        const { isLogin } = this.state;

        // 1.方案一:通过if判断: 逻辑代码非常多的情况
        let welcome = null;
        let btnText = null;
        if (isLogin) {
          welcome = <h2>欢迎回来~</h2>
          btnText = "退出";
        } else {
          welcome = <h2>请先登录~</h2>
          btnText = "登录";
        }

        return (
          <div>
            <div>我是div元素</div>

            {welcome}
            {/* 2.方案二: 三元运算符 */}
            <button onClick={e => this.loginClick()}>{isLogin ? "退出" : "登录"}</button>

            <hr />

            <h2>{isLogin ? "你好啊, admin": null}</h2>

            {/* 3.方案三: 逻辑与&& */}
            {/* 逻辑与: 一个条件不成立, 后面的条件都不会进行判断了 */}
            <h2>{ isLogin && "你好啊, admin" }</h2>
            { isLogin && <h2>你好啊, admin</h2> }
          </div>
        )
      }

      loginClick() {
        this.setState({
          isLogin: !this.state.isLogin
        })
      }
    }

    ReactDOM.render(<App />, document.getElementById("app"));
</script>

v-show的效果实现:

<script type="text/babel">
    class App extends React.Component {
      constructor(props) {
        super(props);

        this.state = {
          isLogin: true
        }
      }

      render() {
        const { isLogin } = this.state;
        const titleDisplayValue = isLogin ? "block": "none";
        return (
          <div>
            <button onClick={e => this.loginClick()}>{isLogin ? "退出": "登录"}</button>
            <h2 style={{display: titleDisplayValue}}>你好啊, admin</h2>
          </div>
        )
      }

      loginClick() {
        this.setState({
          isLogin: !this.state.isLogin
        })
      }
    }

    ReactDOM.render(<App />, document.getElementById("app"));
</script>

7. Context(了解!)

7.1 理解

  • 一种组件间通信方式, 常用于【祖组件】与【孙组件】(跨级组件)间通信

7.2 使用

  1. 创建Context容器对象:

    const XxxContext = React.createContext()

  • 也可以传参对象作为默认值{name: 'admin'},name一般与传入的value对象的key相同
  1. 渲染子组件时,外面包裹xxxContext.Provider, 通过value属性给孙组件传递数据:
<xxxContext.Provider value={数据}>
	子组件
</xxxContext.Provider>
  1. 孙组件读取数据:

1.第一种方式:仅适用于类组件

static contextType = xxxContext  // 声明接收context
this.context // 读取context中的value数据

2.第二种方式: 函数组件与类组件都可以

  • 此方式可以使用多个context嵌套
//一般使用
<xxxContext.Consumer>
    {
        value => ( // value就是context中的value数据
            要显示的内容
        )
    }
</xxxContext.Consumer>

//嵌套context
<xxxContext.Consumer>
    {
        value => ( // value就是context中的value数据
            <xxxContext.Consumer>
                {
                    value => ( // value就是context中的value数据
                        要显示的内容
                    )
                }
            </xxxContext.Consumer>
        )
    }
</xxxContext.Consumer>

注:

  • 在应用开发中一般不用context, 一般都用它的封装react插件

实例1: context一般使用

import React, { Component } from 'react'
import './index.css'

//创建Context对象
const MyContext = React.createContext()
const {Provider, Consumer} = MyContext

export default class A extends Component {

	state = {username:'tom',age:18}

	render() {
		const {username,age} = this.state
		return (
			<div className="parent">
				<h3>我是A组件</h3>
				<h4>我的用户名是:{username}</h4>
				<Provider value={{username,age}}>
					<B/>
				</Provider>
			</div>
		)
	}
}

class B extends Component {
	render() {
		return (
			<div className="child">
				<h3>我是B组件</h3>
				<C/>
			</div>
		)
	}
}

/* class C extends Component {
	//声明接收context
	static contextType = MyContext
	render() {
		const {username,age} = this.context
		return (
			<div className="grand">
				<h3>我是C组件</h3>
				<h4>我从A组件接收到的用户名:{username},年龄是{age}</h4>
			</div>
		)
	}
} */

function C(){
	return (
		<div className="grand">
			<h3>我是C组件</h3>
			<h4>我从A组件接收到的用户名:
			<Consumer>
				{value => `${value.username},年龄是${value.age}`}
			</Consumer>
			</h4>
		</div>
	)
}

实例2: 嵌套(多个)context + 设置默认值

import React, { Component } from 'react';

// 创建Context对象
const UserContext = React.createContext({
  nickname: "aaaa",
  level: -1
})

const ThemeContext = React.createContext({
  color: "black"
})

function ProfileHeader() {
  // jsx -> 嵌套的方式
  return (
    <UserContext.Consumer>
      {
        value => {
          return (
            <ThemeContext.Consumer>
              {
                theme => {
                  return (
                    <div>
                      <h2 style={{color: theme.color}}>用户昵称: {value.nickname}</h2>
                      <h2>用户等级: {value.level}</h2>
                      <h2>颜色: {theme.color}</h2>
                    </div>
                  )
                }
              }
            </ThemeContext.Consumer>
          )
        }
      }
    </UserContext.Consumer>
  )
}

function Profile(props) {
  return (
    <div>
      <ProfileHeader />
      <ul>
        <li>设置1</li>
        <li>设置2</li>
        <li>设置3</li>
        <li>设置4</li>
      </ul>
    </div>
  )
}

export default class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      nickname: "kobe",
      level: 99
    }
  }

  render() {
    return (
      <div>
        <UserContext.Provider value={this.state}>
          <ThemeContext.Provider value={{ color: "red" }}>
            <Profile />
          </ThemeContext.Provider>
        </UserContext.Provider>
      </div>
    )
  }
}

8.收集表单数据 - 受控组件/非受控组件

1.受控组件: 表单中输入类的DOM,随着用户的输入,就会把输入的值维护到状态中。

2.非受控组件: 如果组件中的表单数据,是通过 节点.value “现用现取”那么就是非受控组件。

实例1: 一般input使用(受控组件/非受控组件)

需求: 定义一个包含表单的组件

输入用户名密码后, 点击登录提示输入信息

//1.非受控组件
<div id="test"></div>
<script type="text/babel">
    //非受控组件:如果组件中的表单数据,是通过节点.value“现用现取”那么就是非受控组件。
    //创建组件
    class Login extends React.Component{
        handleSubmit = (event)=>{
            event.preventDefault(); //阻止表单提交-阻止默认行为
            const {username,password} = this;
            alert(`你输入的用户名是:${username.value},你输入的密码是:${password.value}`);
        }
        render(){
            return(
                <form onSubmit={this.handleSubmit}>
                	用户名:<input ref={c => this.username = c} type="text" name="username"/>
                    密码:<input ref={c => this.password = c} type="password" name="password"/>
                    <button>登录</button>
				</form>
			)
		}
	}
    //渲染组件
    ReactDOM.render(<Login/>,document.getElementById('test'));
</script>


//2.受控组件
<div id="test"></div>
<script type="text/babel">
    //受控组件:表单中输入类的DOM,随着用户的输入,就会把输入的值维护到状态中。
    //创建组件
    class Login extends React.Component{
        //初始化状态(表单收集几个数据,我就在状态中准备几组key-value)
        state = {
            username:'', //存储用户名-初始值为空
            password:'' //存储密码-初始值为空
        }

		//保存用户名到状态中-用户名发生改变的回调
		saveUsername = (event)=>{
			this.setState({username:event.target.value})
		}
	
		//保存密码到状态中-密码发生改变的回调
		savePassword = (event)=>{
			this.setState({password:event.target.value})
		}
	
		//表单提交的回调
		handleSubmit = (event)=>{
			event.preventDefault() //阻止表单提交
			const {username,password} = this.state
			alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
		}
	
		render(){
			return(
	    		<form onSubmit={this.handleSubmit}>
	    			用户名:<input onChange={this.saveUsername} type="text" name="username" value={this.state.username}/>
	        		密码:<input onChange={this.savePassword} type="password" name="password" value={this.state.password}/>
	            	<button>登录</button>
				</form>
			)
		}
	}
	//渲染组件
	ReactDOM.render(<Login/>,document.getElementById('test'));
</script>

实例2: select的使用

import React, { PureComponent } from 'react'

export default class App extends PureComponent {
  constructor(props) {
    super(props);

    this.state = {
      fruits: "orange"
    }
  }

  render() {
    return (
      <div>
        <form onSubmit={e => this.handleSubmit(e)}>
          <select name="fruits" 
                  onChange={e => this.handleChange(e)}
                  //设置默认选中的项
                  value={this.state.fruits}>
            <option value="apple">苹果</option>
            <option value="banana">香蕉</option>
            <option value="orange">橘子</option>
          </select>
          <input type="submit" value="提交"/>
        </form>
      </div>
    )
  }

  handleSubmit(event) {
    event.preventDefault();
    console.log(this.state.fruits);
  }

  handleChange(event) {
    this.setState({
      fruits: event.target.value
    })
  }
}

9.高阶函数与函数柯里化

1.高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。

​ 1)若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。

​ 2)若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。

​ 常见的高阶函数有:Promise、bind、setTimeout、数组遍历相关方法,$()等等。

2.函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数,最后统一处理的函数编码形式。

function sum(a){
    return(b)=>{
        return (c)=>{
            return a+b+c;
        }
    }
}

实例: 改写受控组件的案例:保存表单数据到状态中的的函数2合一

<div id="test"></div>
<script type="text/babel">
	//创建组件
	class Login extends React.Component{
        //初始化状态
        state = {
            username:'', //用户名
            password:'' //密码
        }

        //保存表单数据到状态中的函数2合一
        //减少重复代码
        saveFormData = (dataType)=>{
            return (event)=>{
                this.setState({[dataType]:event.target.value})
            }
        }
        //写法2-不用高阶函数且不传参(利用e.target.name)
        // saveFormData = (e)=>{
        // 	// console.log(e.target.value);
        //  //计算属性名
        // 	this.setState({[e.target.name]:e.target.value});
        // }
    	
        //写法3-不用高阶函数,但传2个参数
        //saveFormData = (dataType,event)=>{
        //    this.setState({[dataType]:event.target.value})
        //}
        
        //表单提交的回调
        handleSubmit = (event)=>{
            event.preventDefault(); //阻止表单提交
            const {username,password} = this.state; //不再节点.value获取输入了,去状态中获取
            alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
        }

    	render(){
            return(
                // onChange={this.saveFormData('username')}
                // 此处saveFormData返回的函数作为onChange的回调
                <form onSubmit={this.handleSubmit}>
                	用户名:<input onChange={this.saveFormData('username')} type="text" name="username"/>
                    密码:<input onChange={this.saveFormData('password')} type="password" name="password"/>
                    <button>登录</button>
    			</form>
    
                //写法2
                // <form onSubmit={this.handleSubmit}>
                // 	 用户名:<input onChange={this.saveFormData} type="text" name="username"/>
                // 	 密码:<input onChange={this.saveFormData} type="password" name="password"/>
                // 	 <button>登录</button>
                // </form>
    			
    			//写法3
    			//<form onSubmit={this.handleSubmit}>
    			//	用户名:<input onChange={event => this.saveFormData('username',event)} type="text" name="username"/>
    			//	密码:<input onChange={event => this.saveFormData('password',event)} type="password" name="password"/>
    			//	<button>登录</button>
    			//</form>
    		)
    	}
    }
    //渲染组件
    ReactDOM.render(<Login/>,document.getElementById('test'));
</script>

10.高阶组件HOC

1.定义: 参数为组件,返回值为新组件的函数

2.常见的高阶组件: AntD v3 的 Form.create()(组件) / withRouter(组件) / connect()(组件)

3.组件的本质是函数

4.与高阶函数的关系?

  • 高阶组件是一个特别的高阶函数

  • 接收的是组件函数, 同时返回新的组件函数

5.作用:

  • React 中用于复用组件逻辑的一种高级技巧
  • 对原组件进行加工处理(包裹一层),返回新组件

**实例:**antd v3 的 Form.create()(组件)

//Form.create()(Login), 接收一个Login组件, 返回一个新组件
Form.create = function () {
    const form = 创建一个强大form对象
    return function (FormComponent) { //此为高阶组件
        return class WrapComponent extends Component {
            render () {
                return <Login form={form}/>
            }
        }
    }
}
const LoginWrap = Form.create()(Login)

6.应用

1)props的增强

  • 不修改原有代码的情况下,添加新的props
import React, { PureComponent } from 'react';

// 定义一个高阶组件
function enhanceRegionProps(WrappedComponent) {
  return props => {
    return <WrappedComponent {...props} region="中国"/>
  }
}

class Home extends PureComponent {
  render() {
    return <h2>Home: {`昵称: ${this.props.nickname} 等级: ${this.props.level} 区域: ${this.props.region}`}</h2>
  }
}


class About extends PureComponent {
  render() {
    return <h2>About: {`昵称: ${this.props.nickname} 等级: ${this.props.level} 区域: ${this.props.region}`}</h2>
  }
}


const EnhanceHome = enhanceRegionProps(Home);
const EnhanceAbout = enhanceRegionProps(About);

class App extends PureComponent {
  render() {
    return (
      <div>
        App
        <EnhanceHome nickname="admin" level={90}/>
        <EnhanceAbout nickname="kobe" level={99}/>
      </div>
    )
  }
}

export default App;
  • 利用高阶组件来共享Context
//1.一般写法
import React, { PureComponent, createContext } from 'react';

// 创建Context
const UserContext = createContext({
  nickname: "默认",
  level: -1,
  区域: "中国"
});
class Home extends PureComponent {
  render() {
    return (
      <UserContext.Consumer>
        {
          user => {
            return <h2>Home: {`昵称: ${user.nickname} 等级: ${user.level} 区域: ${user.region}`}</h2>
          } 
        }
      </UserContext.Consumer>
    )
  }
}

class About extends PureComponent {
  render() {
    return (
      <UserContext.Consumer>
        {
          user => {
            return <h2>About: {`昵称: ${user.nickname} 等级: ${user.level} 区域: ${user.region}`}</h2>
          } 
        }
      </UserContext.Consumer>
    )
  }
}

class App extends PureComponent {
  render() {
    return (
      <div>
        App
        <UserContext.Provider value={{nickname: "admin", level: 90, region: "中国"}}>
          <Home/>
          <About/>
        </UserContext.Provider>
      </div>
    )
  }
}

export default App;


//2.高阶组件改进写法
import React, { PureComponent } from 'react';

// 定义一个高阶组件
function enhanceRegionProps(WrappedComponent) {
  return props => {
    return <WrappedComponent {...props} region="中国"/>
  }
}

class Home extends PureComponent {
  render() {
    return <h2>Home: {`昵称: ${this.props.nickname} 等级: ${this.props.level} 区域: ${this.props.region}`}</h2>
  }
}


class About extends PureComponent {
  render() {
    return <h2>About: {`昵称: ${this.props.nickname} 等级: ${this.props.level} 区域: ${this.props.region}`}</h2>
  }
}


const EnhanceHome = enhanceRegionProps(Home);
const EnhanceAbout = enhanceRegionProps(About);

class App extends PureComponent {
  render() {
    return (
      <div>
        App
        <EnhanceHome nickname="admin" level={90}/>
        <EnhanceAbout nickname="kobe" level={99}/>
      </div>
    )
  }
}

export default App;

2)渲染判断鉴权

  • 在开发中,我们可能遇到这样的场景:
    • 某些页面是必须用户登录成功才能进行进入;
    • 如果用户没有登录成功,那么直接跳转到登录页面;
  • 这个时候,我们就可以使用高阶组件来完成鉴权操作:
import React, { PureComponent } from 'react';

class LoginPage extends PureComponent {
  render() {
    return <h2>LoginPage</h2>
  }
}

function withAuth(WrappedComponent) {
  const NewCpn = props => {
    const {isLogin} = props;
    if (isLogin) {
      return <WrappedComponent {...props}/>
    } else {
      return <LoginPage/>
    }
  }

  //定义react浏览器调试界面的组件名称
  NewCpn.displayName = "AuthCpn"

  return NewCpn;
}

// 购物车组件
class CartPage extends PureComponent {
  render() {
    return <h2>CartPage</h2>
  }
}

const AuthCartPage = withAuth(CartPage);

export default class App extends PureComponent {
  render() {
    return (
      <div>
        <AuthCartPage isLogin={true}/>
      </div>
    )
  }
}

3)生命周期劫持

  • 计算组件渲染的时间 - 性能优化
//1.一般写法
import React, { PureComponent } from 'react';

class Home extends PureComponent {

  // 即将渲染获取一个时间 beginTime
  UNSAFE_componentWillMount() {
    this.beginTime = Date.now();
  }

  // 渲染完成再获取一个时间 endTime
  componentDidMount() {
    this.endTime = Date.now();
    const interval = this.endTime - this.beginTime;
    console.log(`Home渲染时间: ${interval}`)
  }

  render() {
    return <h2>Home</h2>
  }
}


class About extends PureComponent {
  // 即将渲染获取一个时间 beginTime
  UNSAFE_componentWillMount() {
    this.beginTime = Date.now();
  }

  // 渲染完成再获取一个时间 endTime
  componentDidMount() {
    this.endTime = Date.now();
    const interval = this.endTime - this.beginTime;
    console.log(`About渲染时间: ${interval}`)
  }

  render() {
    return <h2>About</h2>
  }
}

export default class App extends PureComponent {
  render() {
    return (
      <div>
        <Home />
        <About />
      </div>
    )
  }
}


//2.高阶组件写法
import React, { PureComponent } from 'react';

function withRenderTime(WrappedComponent) {
  return class extends PureComponent {
    // 即将渲染获取一个时间 beginTime
    UNSAFE_componentWillMount() {
      this.beginTime = Date.now();
    }

    // 渲染完成再获取一个时间 endTime
    componentDidMount() {
      this.endTime = Date.now();
      const interval = this.endTime - this.beginTime;
      console.log(`${WrappedComponent.name}渲染时间: ${interval}`)
    }

    render() {
      return <WrappedComponent {...this.props}/>
    }
  }
}

class Home extends PureComponent {
  render() {
    return <h2>Home</h2>
  }
}


class About extends PureComponent {
  render() {
    return <h2>About</h2>
  }
}

const TimeHome = withRenderTime(Home);
const TimeAbout = withRenderTime(About);

export default class App extends PureComponent {
  render() {
    return (
      <div>
        <TimeHome />
        <TimeAbout />
      </div>
    )
  }
}

class Person {
}
console.log(Person.name);

4)ref的转发 - 函数式组件使用ref,获取组件内某个元素的DOM

  • 使用React.forwardRef()高阶组件
import React, { PureComponent, createRef, forwardRef } from 'react';

class Home extends PureComponent {
  render() {
    return <h2>Home</h2>
  }
}

// 高阶组件forwardRef
const Profile = forwardRef(function(props, ref) {
  return <p ref={ref}>Profile</p>
})

export default class App extends PureComponent {
  constructor(props) {
    super(props);

    this.titleRef = createRef();
    this.homeRef = createRef();
    this.profileRef = createRef();
  }

  render() {
    return (
      <div>
        <h2 ref={this.titleRef}>Hello World</h2>
        <Home ref={this.homeRef}/>

        <Profile ref={this.profileRef} name={"why"}/>

        <button onClick={e => this.printRef()}>打印ref</button>
      </div>
    )
  }

  printRef() {
    console.log(this.titleRef.current);
    console.log(this.homeRef.current);
    console.log(this.profileRef.current);
  }
}