React学习笔记

目录

1. React的概述

1.1 React的特点

1.2 React的基本使用

1.3 React手脚架的使用

1.3.1 在脚手架中使用React:

1.4 JSX 的基本使用

1.4.1 使用步骤:

1.4.2 JSX 中使用JavaScript 表达式

1.4.3 JSX 的条件渲染

1.4.4 JSX 的列表渲染

1.4.5 JSX 的样式处理

1.5 React 组件的两种创建方式

1.6 抽离为独立JS 文件

2. React事件处理

2.1 事件绑定

2.2 事件对象

2.3 有状态组件和无状态组件

2.3.1 组件中的state 和setState

2.3.2 从JSX 中抽离事件处理程序:

2.3.3 事件绑定this 指向:

3. 表单处理

3.1 受控组件

3.2 非受控组件

4. React 组件进阶

4.1 组件通讯介绍

 4.2. 组件的props

 4.3. 组件通讯的三种方式

4.3.1 父组件传递数据给子组件

4.3.2 子组件传递数据给父组件

4.3.3 兄弟组件

4.4  Context  

4.5 props 深入

4.5.1 children 属性

4.5.2 props 校验

4.5.3 props 的默认值

 4.6 组件的生命周期

4.6.1 组件的生命周期概述

4.6.2 生命周期的三个阶段

 4.7. render-props和高阶组件

4.7.1 React组件复用概述

4.7.2 render props 模式 

4.7.3  高阶组件


1. React的概述

什么是react?

React 是一个用于构建用户界面的 JavaScript 库

用户界面:HTML页面(前端) React 主要用来写HTML页面,或构建Web应用

如果从 MVC 的角度来看,React 仅仅是视图层(V),也就是只负责视图的渲染,而并非提供了 完整的 M 和 C 的功能。

1.1 React的特点

- 声明式

-  基于组件

- 学习一次,随处使用

声明式:

TableLogic你只需要描述 UI(HTML)看起来是什么样,就跟写HTML一样 React 负责渲染 UI,并在数据变化时更新 UI

const jsx = <div className="app">
<h1>Hello React! 动态变化数据:{count}</h1></div>
基于组件:
   组件是 React 最重要 的内容
   组件表示页面中的部分内容
   组合、复用多个组件,可以实现完整的页面功能
学习一次,随处使用:
  使用 React 可以开发 Web 应用
  使用 React 可以开发移动端原生应用( react-native
  使用 React 可以开发 VR (虚拟现实)应用( react 360

1.2 React的基本使用

安装React:

安装命令: npm ireact react-dom
    react 包是核心,提供创建元素、组件等功能
    react-dom 包提供 DOM 相关功能等
1. 引入react react-dom两个js 文件
<script src="./node_modules/react/umd/react.development.js"></script>
<script src="./node_modules/react-dom/umd/react-dom.development.js"></script>
2. 创建React 元素
3.渲染React 元素到页面中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"></meta>
<meta name="viewport" content="width-device-width,initial-scale=1.0"></meta>
<meta http-equiv="X-UA-Compatible" content="ie=edge"></meta>
<title>01-react的基本使用</title>
</head>

<body>
<div id="root"></div>   

<!--1.  引入react和react-dom两个文件
但是注意: 需要先引入react  在引入react-demo -->
<script src="./node_modules/react/umd/react.development.js"></script>
<script src="./node_modules/react-dom/umd/react-dom.development.js"></script>
<script>
    //2. 创建react元素
    //参数1: 元素名称
    //参数2: 元素属性
    //参数3: 元素的子节点  第三个参数以后的都是子节点
    const title=React.createElement("h1",{
        id:"我是id",
        name:"我是名字"
    },"hello world",React.createElement("span",{id:"span_id"},"我是span"))
    //3.渲染react元素
    //需要渲染的react元素
    //挂载点
    ReactDOM.render(title,document.getElementById("root") )

</script>
</body>
</html>
React.createElement() 说明:
// 返回值: React 元素
// 第一个参数:要创建的 React 元素名称
// 第二个参数:该 React 元素的属性
// 第三个及其以后的参数:该 React 元素的子节点
constel = React.createElement('h1', { title: ' 标题 ' }, 'Hello React')
ReactDOM.render() 说明:
// 第一个参数:要渲染的 React 元素
// 第二个参数: DOM 对象,用于指定渲染到页面中的位置
ReactDOM.render(el, document.getElementById('root'))

1.3 React手脚架的使用

React 脚手架意义:
1. 脚手架是开发现代 Web 应用的必备。
2. 充分利用 Webpack Babel ESLint 等工具辅助项目开发。
3. 零配置,无需手动配置繁琐的工具即可使用。
4. 关注业务,而不是工具配置。
初始化项目,命令: npx create-react-app my-app
启动项目,在项目根目录执行命令: npm start

1.3.1 在脚手架中使用React:

1.  导入 react react-dom 两个包。
2. 调用 React.createElement() 方法创建 react 元素。
3. 调用 ReactDOM.render() 方法渲染 react 元素到页面中。
//1. 导入react
import React from 'react'
import ReactDOM from 'react-dom'

//函数组件:
/* function Hello(){
    return(
        <div>这是我的第一个组件</div>
        )
} */
const Hello=()=><div>这是我的第一个组件</div>
//渲染react元素
ReactDOM.render(<Hello/>,document.getElementById("root"))

1.4 JSX 的基本使用

JSX 简介:

JSX JavaScript XML 的简写,表示在 JavaScript 代码中写 XML HTML )格式的代码。
优势:声明式语法更加直观、与 HTML 结构相同,降低了学习成本、提升开发效率
JSX React 的核心内容。

1.4.1 使用步骤:

1. 使用 JSX 语法创建 react 元素
2. 使用 ReactDOM.render() 方法渲染 react 元素到页面中
const Hello=()=><div>这是我的第一个组件</div>
//渲染react元素
ReactDOM.render(<Hello/>,document.getElementById("root"))
注意点:
1.React 元素的属性名使用驼峰命名法
2. 特殊属性名: class -> className for -> htmlFor tabindex-> tabIndex
3. 没有子节点的 React 元素可以用 /> 结束。
4. 推荐:使用 小括号包裹 JSX ,从而避免 JS 中的自动插入分号陷阱。

1.4.2 JSX 中使用JavaScript 表达式

嵌入JS 表达式

数据存储在JS中

语法:{ JavaScript表达式}

注意:语法中是单大括号,不是双大括号!

//3. 使用jsx创建react元素
const name="tjk"
const title=(
          <h1 className='title'>Hello react {name}</h1>
注意:
单大括号 中可以使用任意的 JavaScript 表达式
JSX 自身也是 JS 表达式
注意: JS 中的对象是一个例外,一般只会出现在 style 属性中
注意: 不能在 {} 中出现语句 (比如: if/for 等)

1.4.3 JSX 的条件渲染

场景: loading 效果
条件渲染:根据条件渲染特定的 JSX 结构
可以使用 if/else 三元运算符 逻辑与运算符 来实现
 
/*条件渲染 */
const isLoading=false
const loadData=()=>{
    if(isLoading){
        return <div>loading......</div>
    }
    return <div>数据加载完成</div>
}

const title2=(
    <h2>条件渲染:
              {loadData()}
          </h2>

ReactDOM.render(title2,document.getElementById("root"))

1.4.4 JSX 的列表渲染

如果要渲染一组数据,应该使用数组的 map() 方法
//1. 导入react
import React from 'react'
import ReactDOM from 'react-dom'

//引入css
import './css/index.css'
/**
 * 列表渲染:
 * 
 */
//歌曲列表
const songs=[
    {id:1,name:"爱囚"},
    {id:2,name:"老鼠爱大米"},
    {id:3,name:"南山南"}
]

const list=(
    <ul className='title' style={{color:"red"}}>
        {songs.map(item=><li key={item.id}>{item.name}</li>)}
    </ul>
)
//渲染react元素
ReactDOM.render(list,document.getElementById("root"))

1.4.5 JSX 的样式处理

行内样式—— style:
类名—— className (推荐)
const list=(
    <ul className='title' style={{color:"red"}}>
        {songs.map(item=><li key={item.id}>{item.name}</li>)}
    </ul>

1.5 React 组件的两种创建方式

使用函数创建组件
函数组件:使用 JS 的函数(或箭头函数)创建的组件
   约定 1 :函数名称必须以 大写字母开头   
   约定 2 :函数组件 必须有返回值 ,表示该组件的结构
   如果返回值为 null ,表示不渲染任何内容
//1. 导入react
import React from 'react'
import ReactDOM from 'react-dom'

//函数组件:
/* function Hello(){
    return(
        <div>这是我的第一个组件</div>
        )
} */
const Hello=()=><div>这是我的第一个组件</div>
//渲染react元素
ReactDOM.render(<Hello/>,document.getElementById("root"))

使用类创建组件

/* 类组件:使用ES6的class创建组件

   1. 类名称必须以大写字母开头

   2. 类组件应该继承React.Component父类,从而可以使用父类中提供的属性和方法

   3. 类组件必须提供render()方法

   4. render方法必须有返回值,表示该组件的结构

*/

//1. 导入react
import React from 'react'
import ReactDOM from 'react-dom'

//类组件:
/* 类组件:使用ES6的class创建组件
   1. 类名称必须以大写字母开头
   2. 类组件应该继承React.Component父类,从而可以使用父类中提供的属性和方法
   3. 类组件必须提供render()方法
   4. render方法必须有返回值,表示该组件的结构
*/

class Hello extends React.Component{
    render(){
        return (
            <div>这是我的第一个类组件</div>
        )
    }
}
//渲染react元素
ReactDOM.render(<Hello/>,document.getElementById("root"))

1.6 抽离为独立JS 文件

1. 创建 Hello.js
2. Hello.js 中导入 React
3. 创建组件(函数或类)
4. Hello.js 中导出该组件
5. index.js 中导入 Hello 组件
6. 渲染组件

hello.js

import React from 'react'

/**
 * 抽离组件到独立的js文件中
 */

//创建组件
class Hello extends React.Component{
    render(){
        return(
            <div>这是我的第一个抽离到js文件中的组件</div>
        )
    }
}

//导出组件
export default Hello

index.js

import React from 'react'
import ReactDOM from 'react-dom'
import Hello from './hello.js'

/**
 * 抽离组件到独立的js文件中
 */

//渲染组件
ReactDOM.render(<Hello/>,document.getElementById("root"))

2. React事件处理

2.1 事件绑定

- React事件绑定处理和DOM事件语法相似

- 语法:on+事件名称={事件处理程序}  比: onClick={()=》{}}

- 注意:React事件采用驼峰命名法 onClick

在类组件中绑定事件

import React from 'react'
import ReactDOM from 'react-dom'

/**
 * React事件处理
 * 类组件事件处理
 */
class App extends React.Component{
    handleClick(){
        alert("点我干啥???")
    }
    render(){
        return(
            <button onClick={this.handleClick}>点击一下你的快乐</button>
        )
    }
}

ReactDOM.render(<App />,document.getElementById("root"))

在函数中绑定事件

import React from 'react'
import ReactDOM from 'react-dom'

/**
 * React事件处理
 * 函数组件事件处理
 */
function App(){

   function handleClick(){
        alert("点我干啥???")
    }

    return(
        <button onClick={handleClick}>点击一下你的快乐</button>
    )
}


ReactDOM.render(<App />,document.getElementById("root"))

2.2 事件对象

可以通过 事件处理程序的参数 获取到事件对象
React 中的事件对象叫做: 合成事件 (对象)
合成事件:兼容所有浏览器,无需担心跨浏览器兼容性问题
import React from 'react'
import ReactDOM from 'react-dom'

/**
 * React事件对象
 */
 class App extends React.Component{
    handleClick(e){
        e.preventDefault()
        alert("点我干啥???")
    }
    render(){
        return(
            <a href='http://itcast.cn' onClick={this.handleClick}>点击一下你的快乐</a>
        )
    }
}

ReactDOM.render(<App />,document.getElementById("root"))

本来点击按钮后会进行链接跳转,但是因为有了事件对象组织了浏览器的默认行为,所以这里只会进行alert

2.3 有状态组件和无状态组件

函数组件又叫 无状态组件   类组件又叫做 有状态组件

状态(state)即数据

函数组件没有自己的状态,只负责数据的展示(静)

类组件有自己的状态,负责更新ui,让页面动起来

2.3.1 组件中的state setState

state的基本使用:

状态( state )即数据,是组件内部的 私有 数据,只能在组件内部使用
state 的值是对象 ,表示一个组件中可以有多个数据
import React from 'react'
import ReactDOM from 'react-dom'

/**
 * state 状态的基本使用
 */
 class App extends React.Component{
    //第一种
/*     constructor(){
        super()

        this.state={
            count:0
        }
    } */

    //第二种:
    state={
        count:10
    }
    handleClick(e){
        e.preventDefault()
        alert("点我干啥???")
    }
    render(){
        return(
           <h1>计数器:{this.state.count}</h1>
        )
    }
}

ReactDOM.render(<App />,document.getElementById("root"))

状态即数据
状态是私有的,只能在组件内部使用
通过 this.state 来获取状态
setState() 修改状态:
  状态是可变的
  语法: this.setState({ 要修改的数据 })
  注意: 不要直接修改 state 中的值,这是错误的!!!
  setState() 作用: 1. 修改 state 2. 更新 UI
  思想: 数据驱动视图

import React from 'react'
import ReactDOM from 'react-dom'

/**
 * setState 状态的基本使用
 */
 class App extends React.Component{

    //第二种:
    state={
        count:0
    }
    render(){
        return(
            <div>
           <h1>计数器:{this.state.count}</h1>
           <button onClick={()=>{
               this.setState({
                   count:this.state.count+1
               })
           }}>+1</button>
           </div>
        )
    }
}

ReactDOM.render(<App />,document.getElementById("root"))

2.3.2 从JSX 中抽离事件处理程序:

    JSX 中掺杂过多 JS 逻辑代码,会显得非常混乱
    推荐: 将逻辑抽离到单独的方法中 ,保证 JSX 结构清晰
import React from 'react'
import ReactDOM from 'react-dom'

/**
 * setState 状态的基本使用
 */
 class App extends React.Component{

    //第二种:
    state={
        count:0
    }

    onIncrement(){
        console.log("事件处理程序中的");
        this.setState({
            count:this.state.count+1
        })
    }
    render(){
        return(
            <div>
           <h1>计数器:{this.state.count}</h1>
           <button onClick={this.onIncrement}>+1</button> 
           
           
       {/*     onClick={()=>{
               this.setState({
                   count:this.state.count+1
               })
           }}>+1  */}


           </div>
        )
    }
}

ReactDOM.render(<App />,document.getElementById("root"))

this指向不明确,出现undefined

2.3.3 事件绑定this 指向:

1. 箭头函数
2. Function.prototype.bind()
3. class 的实例方法
箭头函数
 利用箭头函数自身不绑定this的特点
 render() 方法中的this 为组件实例,可以获取到setState()
import React from 'react'
import ReactDOM from 'react-dom'

/**
 * setState 状态的基本使用
 */
 class App extends React.Component{

    //第二种:
    state={
        count:0
    }

    onIncrement(){
        console.log("事件处理程序中的");
        this.setState({
            count:this.state.count+1
        })
    }
    render(){
        return(
            <div>
           <h1>计数器:{this.state.count}</h1>
           <button onClick={()=>this.onIncrement()}>+1</button> 
            </div>
        )
    }
}

ReactDOM.render(<App />,document.getElementById("root"))

 

3. 表单处理

3.1 受控组件

HTML 中的表单元素是可输入的,也就是有自己的可变状态 ,而React 中可变状态通常保存在 state 中,并且只能通过 setState() 方法来修改 React将 state 与表单元素值value绑定到一起,由 state 的值来控制表单元素的值 受控组件:其值受到 React 控制的表单元素

步骤: 

1. state 中添加一个状态,作为表单元素的 value 值(控制表单元素值的来源)
2. 给表单元素绑定 change 事件,将表单元素的值设置为 state 的值(控制表单元素值的变化)
示例:
1. 文本框,富文本框、下拉框
2. 复选框
import React from 'react'
import ReactDOM from 'react-dom'

/**受控组件: 其值受到React控制的表单元素 
 * 操作文本框的值
 */
 class App extends React.Component{

    //第二种:
    state={
        txt:'',
        content:'',
        select:'',
        isChecked:''
    }

    handlechange = (e) =>{
        this.setState({
            txt:e.target.value
        })
    }

    handleContent = (e) =>{
        this.setState({
            content:e.target.value
        })
    }

    handleSelect =e =>{
        this.setState({
            select:e.target.value
        })
    handleChecked = e =>{
        this.setState({
            isChecked:e.target.checked
        })
    }
    
    render(){
        return(
            <div>
            {/* <!--文本框--> */}
            姓名:   <input type="text" value={this.state.txt} onChange={this.handlechange}/>
            {/* 富文本框 */}
            描述:<textarea value={this.state.content} onChange={this.handleContent}> </textarea>
            {/* 下拉框 */}
            归属地:<select value={this.state.select} onChange={this.handleSelect}>
                       <option value="sh">上海</option>
                       <option value="sz">深圳</option>
                       <option value="bj">北京</option>
                    </select>
            性别:<input type="checkbox" checked={this.state.isChecked} onChange={this.handleChecked}/>
            </div>
        )
    }
}
ReactDOM.render(<App />,document.getElementById("root"))

 多表单元素优化步骤:

1. 给表单元素添加 name 属性,名称与 state 相同
2. 根据表单元素类型获取对应值
3. change 事件处理程序中通过 [name] 来修改对应的 state
import React from 'react'
import ReactDOM from 'react-dom'

/**受控组件: 其值受到React控制的表单元素 
 * 操作文本框的值
 */
 class App extends React.Component{

    //第二种:
    state={
        txt:'',
        content:'',
        select:'',
        isChecked:''
    }

    handleFrom = (e) =>{
        //获取当前的dom对象
        const target=e.target
        //根据类型获取值
        const value=target.type==="checkbox"
                                 ?target.checked:target.value
        //获取name
        const name=target.name
        this.setState({
            [name]:value
        })
    }
    
    render(){
        return(
            <div>
            {/* <!--文本框--> */}
            姓名:   <input type="text" name='txt' value={this.state.txt} onChange={this.handleFrom}/>
            {/* 富文本框 */}
            描述:<textarea name="content" value={this.state.content} onChange={this.handleFrom}> </textarea>
            {/* 下拉框 */}
            归属地:<select name='select' value={this.state.select} onChange={this.handleFrom}>
                       <option value="sh">上海</option>
                       <option value="sz">深圳</option>
                       <option value="bj">北京</option>
                    </select>
            性别:<input type="checkbox" name='isChecked' checked={this.state.isChecked} onChange={this.handleFrom}/>
            </div>
        )
    }
}
ReactDOM.render(<App />,document.getElementById("root"))

3.2 非受控组件

说明:借助于 ref ,使用原生 DOM 方式来获取表单元素值
ref 的作用:获取 DOM 或组件
//1. 导入react
import React from 'react'
import ReactDOM from 'react-dom'
/*
非受控组件
*/

class App extends React.Component{
    constructor(){
        super()
        this.txtref=React.createRef()
    }
    //获取文本框的值
    getTxt=()=>{
        console.log("文本框的值为:",this.txtref.current.value);
    }
    render(){
        return(
            <div>
                <input type="text" ref={this.txtref}/>
                <button onClick={this.getTxt}>获取文本框的内容</button>
            </div>
        )
    }
}

//渲染react元素
ReactDOM.render(<App/>,document.getElementById("root"))

发表评论案例:

//1. 导入react
import React from 'react'
import ReactDOM from 'react-dom'


class App extends React.Component{
    state={
        comments:[
            {id:1,name:"jack",content:"沙发"},
            {id:2,name:"tom",content:"沙发aaa"},
            {id:3,name:"tjk",content:"沙发bbb"},
            {id:4,name:"zjj",content:"沙发ccc"},
        ],
        userName:'',
        userContent:''
    }
    renderList=()=>{
        const {comments}=this.state
        if(comments.length===0){
            return (<div className='no-comment'>暂无评论,快去评论吧</div>)
        }else{
            return (  <ul>
                {comments.map(item=>(
                        <li key={item.id}>
                        <h3>评论人:{item.name}</h3>
                        <p>评论内容:{item.content}</p>
                    </li>
                ))}
                </ul>)
        }
        
    }
    handleFrom=(e)=>{
        const {name,value}=e.target
        this.setState({
            [name]:value
        })
    }
    addComments=()=>{
        const {userName,userContent,comments}=this.state
        // console.log(username,userContent)
        const newComments=[{id:Math.random(),
                            name:userName,
                            content:userContent},
                            ...comments]
        //非空校验:
        if(userName.trim()===''||userContent.trim()===''){
            alert("请进行输入")
            return;
        }
        this.setState({
            comments:newComments,
            userName:'',
            userContent:'',
            
        })

    }
    render(){
        const {userName,userContent}=this.state
        return(
            <div className='app'>
                <div>
                    <input className='user' 
                           type="text" 
                           placeholder='请输入评论人' 
                           value={userName} 
                           name="userName" 
                           onChange={this.handleFrom}/ >
                    <br/>
                    <textarea className='content' cols={30} rows="10" placeholder='请输入评论内容' 
                              value={userContent} 
                              name="userContent" onChange={this.handleFrom}/>
                    <br/>
                    <button onClick={this.addComments}>发表评论</button>
                </div>
                {/* 通过条件渲染 */}
                {
                    this.renderList()
                } 
            </div>
        )
    }
}

//渲染react元素
ReactDOM.render(<App/>,document.getElementById("root"))

4. React 组件进阶

4.1 组件通讯介绍

组件 是独立且封闭的单元,默认情况下,只能使用组件自己的数据。在组件化过程中,我们将一个完整的功能拆分成多个组件,以更好的完成整个应用的功能。而在这个过程中,多个组件之间不可避免的要共享某些数据。为了实现这些功能,就需要打破组件的独立封闭性,让其与外界沟通。这个过程就是 组件通讯

 4.2. 组件的props

组件是封闭的,要接收外部数据应该通过 props 来实现
props 的作用:接收传递给组件的数据
传递数据: 给组件标签添加属性
接收数据:函数组件通过 参数 props 接收数据,类组件通过 this.props 接收数据

 特点:

1. 可以给组件传递任意类型的数据
2.props 只读 的对象,只能读取属性的值,无法修改对象
3. 注意:使用类组件时,如果写了构造函数, 应该将 props 传递给 super() ,否则,无法在构造函数中获取到 props

 4.3. 组件通讯的三种方式

组件之间的通讯分为 3 种:
1. 父组件 -> 子组件
2. 子组件 -> 父组件
3. 兄弟组件

4.3.1 父组件传递数据给子组件

1. 父组件提供要传递的 state 数据
2. 给子组件标签添加属性,值为 state 中的数据
3. 子组件中通过 props 接收父组件中传递的数据

import React from 'react'
import ReactDOM from 'react-dom'



 class Parent extends React.Component{

    state={
        name:"tjk"
    }
    render(){
        return (
            <div>
            <Child name={this.state.name}/>
        </div>
        )
    }
} 
 const Child=(props)=>{
    return(
        <div>
           子组件收到的数据:{props.name}
        </div>
    )
} 
ReactDOM.render(<Parent/>,document.getElementById("root"))

4.3.2 子组件传递数据给父组件

思路:利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数。
1. 父组件提供一个回调函数(用于接收数据)
2. 将该函数作为属性的值,传递给子组件

4.3.3 兄弟组件

共享状态 提升到最近的公共父组件中,由 公共父组件 管理这个状态
思想: 状态提升
公共父组件职责: 1. 提供共享状态 2. 提供操作共享状态的方法
要通讯的子组件只需通过 props 接收状态或操作状态的方法

4.4  Context  

思考: App 组件要传递数据给 Child 组件,该如何处理?
   处理方式:使用 props 一层层组件往下传递(繁琐)
   更好的姿势:使用 Context
   作用:跨组件传递数据 (比如:主题、语言等)
使用步骤:
   1. 调用 React. createContext() 创建 Provider (提供数据)和 Consumer (消费数据)两个组件。
                           const{ Provider , Consumer } = React.createContext()
   2. 使用 Provider 组件作为父节点。
<Provider>
  <div className="App">
  <Child1 /> 
  </div>
</Provider>
3. 设置 value 属性,表示要传递的数据。
        <Provider value="pink" >
4. 调用 Consumer 组件接收数据。
<Consumer>
   {data => <span>data参数表示接收到的数据--{data}</span>}
</Consumer>

总结:

1. 如果两个组件是远方亲戚(比如,嵌套多层)可以使用 Context 实现组件通讯
2.Context 提供了两个组件: Provider Consumer
3.Provider 组件:用来提供数据
4.Consumer 组件:用来消费数据
import React from 'react'
import ReactDOM from 'react-dom'

import './index.css'
/* 
  Context
*/

// 创建context得到两个组件
const { Provider, Consumer } = React.createContext()

class App extends React.Component {
  render() {
    return (
        <Provider value={"pink"}>
        <div className="app">
          <Node />
        </div>
        </Provider>
    )
  }
}

const Node = props => {
  return (
    <div className="node">
      <SubNode />
    </div>
  )
}

const SubNode = props => {
  return (
    <div className="subnode">
      <Child />
    </div>
  )
}

const Child = props => {
  return (
    <div className="child">
    <Consumer>
        {
            data=> <span>我是子节点---{data} </span>
        }
    </Consumer>
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

4.5 props 深入

4.5.1 children 属性

children 属性:表示组件标签的子节点。当组件标签有子节点时,props就会有该属性

children 属性与普通的props 一样,值可以是任意值( 文本、React元素、组件,甚至是函数
//1. 导入react
import React from 'react'
import ReactDOM from 'react-dom'
//children属性

const App=props=>{
    console.log(props);
    return(
        <div>
            <h1>组件标签的子节点:{props.children}</h1>
        </div>
    )
}
   
//渲染react元素
ReactDOM.render(<App>我是子节点</App>,document.getElementById("root"))

4.5.2 props 校验

对于组件来说,props 是外来的,无法保证组件使用者传入什么格式的数据

如果传入的数据格式不对,可能会导致组件内部报错

关键问题:组件的使用者不知道明确的错误原因

解决: 

props 校验:允许在创建组件的时候,就指定 props 的类型、格式等
作用:捕获使用组件时因为 props 导致的错误,给出明确的错误提示,增加组件的健壮性
使用步骤:
1. 安装包 prop-types yarn add prop-types / npm i prop-types
2. 导入 prop-types
3. 使用 组件名 .propTypes= {} 来给组件的 props 添加校验规则
4. 校验规则 通过 PropTypes 对象来指定

 约束规则:

1.常见类型:arrayboolfuncnumberobjectstring

2.React元素类型:element

3.必填项:isRequired

4.特定结构的对象: shape({ })
// 添加props校验
// 属性 a 的类型:      数值(number)
// 属性 fn 的类型:     函数(func)并且为必填项
// 属性 tag 的类型:    React元素(element)
// 属性 filter 的类型: 对象({area: '上海', price: 1999})
App.propTypes = {
  a: PropTypes.number,
  fn: PropTypes.func.isRequired,
  tag: PropTypes.element,
  filter: PropTypes.shape({
    area: PropTypes.string,
    price: PropTypes.number
  })
}

4.5.3 props 的默认值

场景:分页组件每页显示条数

作用:给props 设置默认值,在未传入props 时生效

 4.6 组件的生命周期

4.6.1 组件的生命周期概述

意义:组件的生命周期有助于理解组件的运行方式、完成更复杂的组件功能、分析组件错误原因

组件的生命周期:组件从被创建到挂载到页面中运行,再到组件不用时卸载的过程

生命周期的每个阶段总是伴随着一些方法调用,这些方法就是生命周期的钩子函数

钩子函数的作用:为开发人员在不同阶段操作组件提供了时机。

只有类组件才有生命周期。

4.6.2 生命周期的三个阶段

1.每个阶段的执行时机

2.每个阶段钩子函数的执行顺序

3.每个阶段钩子函数的作用

1. 创建时(挂载阶段)

执行时机:组件创建时(页面加载时)

执行顺序:

2. 更新时(更新阶段)

执行时机:1. setState() 2. forceUpdate() 3. 组件接收到新的props

说明:以上三者任意一种变化,组件就会重新渲染

执行顺序:

 

3. 卸载时(卸载阶段)

执行时机:组件从页面中消失 

 4.7. render-props和高阶组件

4.7.1 React组件复用概述

思考:如果两个组件中的部分功能相似或相同,该如何处理?

处理方式:复用相似的功能(联想函数封装)

复用什么?1. state 2.操作state的方法(组件状态逻辑)

两种方式:1. render props模式2. 高阶组件(HOC

注意:这两种方式 不是新的 API ,而是利用 React 自身特点的编码技巧,演化而成的固定模式(写法)

4.7.2 render props 模式 

思路分析:
- 思路:将要复用的 state 和操作 state 的方法封装到一个组件中
- 问题 1 :如何拿到该组件中复用的 state
          在使用组件时,添加一个值为 函数的 prop ,通过函数 参数 来获取(需要组件内部实现)
- 问题 2 :如何渲染任意的 UI
          使用 该函数的返回值 作为要渲染的 UI 内容(需要组件内部实现)

<Mouse render={(mouse) => {}}/>
<Mouse render={(mouse) => (
    <p>鼠标当前位置{mouse.x},{mouse.y}</p>)}/>

使用步骤

1. 创建 Mouse 组件,在组件中提供复用的 状态逻辑 代码( 1. 状态 2. 操作状态的方法)
2. 将要 复用的状态 作为 props.render( state ) 方法的参数,暴露到组件外部
3. 使用 props.render() 返回值 作为要渲染的内容
import React from 'react'
import ReactDOM from 'react-dom'
import img from './image/cat.png'
import PropTypes from 'prop-types'

//创建Mouse组件
class Mouse extends React.Component{
    //鼠标位置state
    state={
        x:0,
        y:0
    }
    //鼠标移动事件的事件处理程序
    handleMouseMove=e=>{
        this.setState({
            x:e.clientX,
            y:e.clientY
        })
    }
    //监听鼠标移动事件
    //这个钩子函数  是在组件初次被渲染的时候进行执行
    componentDidMount(){

        window.addEventListener("mousemove",this.handleMouseMove)

    }

    render(){
      return this.props.render(this.state)
     
    }
}


class App extends React.Component{
    
    render(){
        return (
              <div> 
                  <h1>render props模式</h1>
                  { <Mouse render={(mouse)=>{
                      return(
                          <p>
                              鼠标位置:{mouse.x} {mouse.y}
                          </p>
                      )
                  }}
                  /> }

              </div>
    }
} 

ReactDOM.render(<App/>,document.getElementById("root"))
演示 Mouse 组件的复用
<Mouse render={(mouse) => <p>鼠标当前位置{mouse.x},{mouse.y}</p>}/>
children 代替 render 属性
  注意:并不是该模式叫 render props 就必须使用名为 render prop ,实际上可以使用任意名称的 prop
  把 prop 是一个函数并且告诉组件要渲染什么内容的技术叫做: renderprops 模式
  推荐:使用children代替render 属性
import React from 'react'
import ReactDOM from 'react-dom'
import img from './image/cat.png'
import PropTypes from 'prop-types'


//创建Mouse组件
class Mouse extends React.Component{
    //鼠标位置state
    state={
        x:0,
        y:0
    }
    //鼠标移动事件的事件处理程序
    handleMouseMove=e=>{
        this.setState({
            x:e.clientX,
            y:e.clientY
        })
    }
    //监听鼠标移动事件
    //这个钩子函数  是在组件初次被渲染的时候进行执行
    componentDidMount(){

        window.addEventListener("mousemove",this.handleMouseMove)

    }

    //推荐:在组件卸载时移除事件绑定
    componentWillUnmount(){
      window.removeEventListener("mousemove",this.handleMouseMove)
    }
    render(){
      //  return this.props.render(this.state)
      return this.props.children(this.state)
    }
}


//添加props校验
Mouse.propTypes={
    children:PropTypes.func.isRequired
}



 class App extends React.Component{
    
    render(){
        return (
              <div> 
                  <h1>render props模式</h1>
                  {/* <Mouse render={(mouse)=>{
                      return(
                          <p>
                              鼠标位置:{mouse.x} {mouse.y}
                          </p>
                      )
                  }}
                  /> */}
                  <Mouse >
                     {mouse=>{
                        return (
                            <p>
                              鼠标位置:{mouse.x} {mouse.y}
                            </p>
                        ) 
                    }} 
                  </Mouse>



                  {/*猫捉老鼠*/}
                  <Mouse>
                      {
                         mouse=>{
                            return <img src={img} alt="猫"  style={{
                                position:"absolute",
                                top:mouse.y-64,
                                left:mouse.x-64,
                                width:150,
                                height: 150,                         
                            }}
                          />
                       
                      }
                    }
                  </Mouse>
              </div>
              
            
            )
    }
} 

ReactDOM.render(<App/>,document.getElementById("root"))
代码优化:
1. 推荐:给 render props 模式添加 props 校验
2. 应该在组件卸载时解除 mousemove 事件绑定
1. 
//添加props校验
Mouse.propTypes={
    children:PropTypes.func.isRequired
}


2. 
//推荐:在组件卸载时移除事件绑定
    componentWillUnmount(){
      window.removeEventListener("mousemove",this.handleMouseMove)
    }

4.7.3  高阶组件

概述:
 目的: 实现状态逻辑复用
 采用 包装(装饰)模式 ,比如说:手机壳
 手机:获取保护功能
 手机壳:提供保护功能
 高阶组件就相当于手机壳,通过包装组件,增强组件功能
思路分析:
  高阶组件 HOC Higher-OrderComponent 是一个函数 ,接收要包装的组件,返回增强后的组件
  高阶组件内部 创建一个类组件 ,在这个类组件中 提供复用的状态逻辑 代码,通过 prop将复用的状态传递给被包装组件 WrappedComponent
constEnhancedComponent= withHOC(WrappedComponent)

// 高阶组件内部创建的类组件:
class Mouse extends React.Component{
     render() {
           return <WrappedComponent{...this.state} />
  }
}
使用步骤
1. 创建一个函数,名称约定 with 开头
2. 指定函数参数,参数应该以大写字母开头(作为要渲染的组件)
3. 在函数内部创建一个类组件, 提供复用的状态逻辑代码 ,并返回
4. 在该组件中,渲染参数组件,同时将状态通过 prop 传递给参数组件
import React from 'react'
import ReactDOM from 'react-dom'
import img from './image/cat.png'

/**
 * 高阶组件
 */
//创建高阶组件
function withMouse(WrappedComponent){
    //该组件提供复用的状态逻辑
    class Mouse extends React.Component{
      //鼠标状态
      state={
          x:0,
          y:0
      }

      handleMouseMove=e=>{
          this.setState({
              x:e.clientX,
              y:e.clientY
          })
      }
      //控制鼠标状态逻辑  绑定事件
      componentDidMount(){
          window.addEventListener("mousemove",this.handleMouseMove)
      }
      //解绑事件
      componentWillUnmount(){
          window.removeEventListener("mousemove",this.handleMouseMove)
      }

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

    }

    //返回创建好的组件
    return Mouse
}

const Position=props=>(
    <p>
        鼠标当前的位置:(x:{props.x},y:{props.y})
    </p>
)

const Cat=props=>(
   <img src={img} style={{
       position:"absolute",
       top:props.y-64,
       left:props.x-64,
       width:150,
       height:150,

    } }/>

)

const MouseCat=withMouse(Cat)

//获取增强后的组件
const MousePosition=withMouse(Position)
 class App extends React.Component{
    
    render(){
        return (
              <div> 
                  <h1>高阶组件</h1>
                  {/* 渲染增强后的组件 */}
                  <MousePosition></MousePosition>
                  <MouseCat></MouseCat>
              </div> 
            
            )
    }
} 

ReactDOM.render(<App/>,document.getElementById("root"))
设置 displayName:
  使用高阶组件存在的问题:得到的两个组件名称相同
  原因:默认情况下, React 使用 组件名称 作为 displayName
  解决方式:为高阶组件设置 displayName 便于调试时区分不同的组件
  displayName 的作用:用于设置调试信息( ReactDeveloper Tools 信息)
  设置方式:

 传递props:

  问题: props 丢失
  原因:高阶组件没有往下传递 props
  解决方式:渲染 WrappedComponent 时,将 state this.props 一起传递给组件
  传递方式:
<WrappedComponent{...this.state} {...this.props} />

5. React 原理:

5.1 setState() 的说明

5.1.1 更新数据

 setState() 异步 更新数据的
 注意:使用该语法时,后面的 setState() 不要依赖于前面的 setState()
 可以多次调用 setState() ,只会触发一次重新渲染
this.state= { count: 1 }
this.setState({
  count: this.state.count+ 1
})
console.log(this.state.count) // 1

5.1.2 推荐语法

 推荐:使用 setState((state, props) => {}) 语法
 参数 state :表示最新的 state
 参数 props :表示最新的 props
this.setState((state, props) => {
   return {
     count: state.count+ 1
  }
 })

console.log(this.state.count) // 1
import React from 'react'
import ReactDOM from 'react-dom'


 class App extends React.Component{
   state={
     count:1
   }
   handleclick=()=>{
     //此处更新state
    /* this.setState({
       count:this.state.count+1//1+1
     })

     this.setState({
       count:this.state.count+1//1+1
     })*/

     //推荐语法
     //注意:这种方法也是异步更新的state的
     this.setState((state,props)=>{
       return{
         count:state.count+1 //1+1
       }
     },()=>{
       console.log("状态更新完成");
     })

     this.setState((state,props)=>{
      return{
        count:state.count+1 //2+1
      }
    })

     console.log("count:",this.state.count);
   }
    
    render(){
        return (
              <div> 
                  <h1>计数器:{this.state.count}</h1>
                  <button onClick={this.handleclick}>+1</button>
                 
              </div> 
            
            )
    }
} 

ReactDOM.render(<App/>,document.getElementById("root"))

5.1.3 第二个参数

   场景:在状态更新(页面完成重新渲染)后立即执行某个操作
   语法: setState(updater[, callback])

5.2. JSX 语法的转化过程 

   JSX 仅仅是 createElement() 方法的语法糖(简化语法)
   JSX 语法被 @babel/preset-react 插件编译为 createElement() 方法
   React 元素:是一个对象,用来描述你希望在屏幕上看到的内容

5.3. 组件更新机制 

   setState() 的两个作用: 1. 修改 state 2. 更新组件( UI
   过程:父组件重新渲染时,也会重新渲染子组件。但只会渲染 当前组件子树 (当前组件及其所有子组件)

5.4. 组件性能优化 

5.4.1 减轻state

   减轻 state :只存储跟组件渲染相关的数据(比如: count/ 列表数据 / loading 等)
   注意:不用做渲染的数据不要放在 state 中,比如定时器 id
   对于这种需要在多个方法中用到的数据,应该放在 this

 5.4.2 避免不必要的重新渲染

     组件更新机制:父组件更新会引起子组件也被更新,这种思路很清晰
     问题:子组件没有任何变化时也会重新渲染
  如何避免不必要的重新渲染呢?
     解决方式:使用 钩子函数 shouldComponentUpdate(nextProps, nextState)
     作用:通过返回值决定该组件是否重新渲染,返回 true 表示重新渲染, false 表示不重新渲染
     触发时机:更新阶段的钩子函数,组件重新渲染前执行( shouldComponentUpdate render

 案例:随机数:

 5.4.3 纯组件

  纯组件: PureComponent React.Component 功能相似
  区别: PureComponent 内部自动实现了 shouldComponentUpdate 钩子,不需要手动比较
  原理:纯组件内部通过分别 对比 前后两次 props state 的值,来决定是否重新渲染组件

 

说明:纯组件内部的对比是 shallow compare (浅层对比)
对于值类型来说:比较两个值是否相同(直接赋值即可,没有坑)
说明:纯组件内部的对比是 shallow compare (浅层对比)
对于 引用类型 来说:只比较对象的引用(地址)是否相同

 

   说明:纯组件内部的对比是 shallow compare (浅层对比)
    对于引用类型来说:只比较对象的引用(地址)是否相同
    注意: state props 中属性值为引用类型时,应该创建新数据,不要直接修改原数据!( 示例

5.5 虚拟DOM Diff 算法

   React 更新视图的思想是:只要 state 变化就重新渲染视图
   特点:思路非常清晰
   问题:组件中只有一个 DOM 元素需要更新时,也得把整个组件的内容重新渲染到页面中? 不是
   理想状态: 部分更新 ,只更新变化的地方。
   问题: React 是如何做到部分更新的? 虚拟 DOM 配合 Diff 算法

 

虚拟DOM:本质上就是一个JS 对象,用来描述你希望在屏幕上看到的内容(UI)。

 执行过程:

1. 初次渲染时, React 会根据初始 state Model ),创建一个 虚拟 DOM 对象(树)
2. 根据虚拟 DOM 生成真正的 DOM ,渲染到页面中。
3. 当数据变化后( setState() ),重新根据新的数据,创建新的虚拟 DOM 对象(树)。
4. 与上一次得到的虚拟 DOM 对象,使用 Diff 算法 对比(找不同),得到需要更新的内容。
5. 最终, React 只将 变化的内容 更新( patch )到 DOM 中,重新渲染到页面。

代码演示:

   组件 render() 调用后,根据 状态 JSX 结构 生成虚拟 DOM 对象
   示例中,只更新 p 元素的文本节点内容

 

React 原理揭秘:
1. 工作角度:应用第一,原理第二。
2. 原理有助于更好地理解 React 的自身运行机制。
3.setState() 异步更新数据。
4. 父组件更新导致子组件更新,纯组件提升性能。
5. 思路清晰简单为前提,虚拟 DOM Diff 保效率。
6. 虚拟 DOM state + JSX
7. 虚拟 DOM 的真正价值从来都不是性能。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值