Skip to content

React 基础学习

第一章:认识 React

一、是什么?

1. 什么是 React?

以前

React 是一个用于构建用户界面的 JavaScript 库(是一个将数据渲染为 HTML 视图的开源 JS 库)。

它遵循基于组件的方法,有助于构建可重用的 UI 组件。它用于开发复杂的交互式的 web 和移动 UI。

现在

React 用于构建 Web 和原生交互界面的库。

"原生交互界面"是指 React Native。这是 React 的一个版本,它允许使用相同的 React 代码来构建 iOS 和 Android 的原生应用。这意味着可以使用 React 和 JavaScript 来编写移动应用,而不需要学习如 Swift、Objective-C 或 Java 这样的原生编程语言。

2. React 有什么特点?

① 使用虚拟 DOM,而不是真正的 DOM。

② 声明式编码,组件化编码。

③ 开发效率提高。

④ 它可以用服务器渲染。

⑤ 它遵循单向数据流或数据绑定:单向数据流指 React 的数据流动是单向的,从父组件流向子组件。

3. React 的一些主要优点?

① 它提高了应用的性能。

② 由于使用 JSX,代码的可读性更好。

③ 使用 React,编写 UI 测试用例变得非常容易。

④ 可以方便在客户端和服务器端使用:React 的同构或者通用 JavaScript 的特性。这意味着你可以在服务器端使用 React 代码进行渲染,然后在客户端接管这些界面。这种方式的好处是首次加载页面时可以快速显示内容,提高用户体验,同时也有利于搜索引擎优化(SEO)。

二、快速入门

1. 先倒入三个包

  • react.development.js:React 库的开发版本。它包含了完整的警告和错误消息,对于在开发环境中调试 React 应用非常有用。全局有一个 React 对象。
  • react-dom.development.js:ReactDOM 库的开发版本。ReactDOM 提供了操作 DOM 的方法,使 React 能够更新和渲染在 Web 浏览器中的 DOM 元素。全局有一个 ReactDOM 对象。
  • babel.min.js:Babel 是一个 JavaScript 编译器,它可以将 ES6+(包括 JSX)代码转换为兼容更多浏览器的 ES5 代码。

必须要先引入 react.development.js,后引入 react-dom.development.js。babel.min.js 无顺序要求。

2. 创建一个容器

3. 创建虚拟 DOM,渲染到容器中

jsx
<body>
    {/* 准备好容器 */}
    <div id="test"></div>
</body>

{/* 引入依赖 ,引入的时候,必须就按照这个步骤 */}
<script src="../js/react.development.js" type="text/javascript"></script>
<script src="../js/react-dom.development.js" type="text/javascript"></script>
<script src="../js/babel.min.js" type="text/javascript"></script>

{/* 这里使用了babel用来解析jsx语法 */}
<script type="text/babel">
    {/* 1.创建虚拟DOM */}
    const VDOM = <h1>Hello</h1>  {/*  这个地方使用的是JSX语法,不需要加"" */}
    {/* 2.渲染,如果有多个渲染同一个容器,后面的会将前面的覆盖掉 */}
    ReactDOM.render(VDOM, document.getElementById("test"));        
</script>

三、jsx 语法

JSX 是 一种 JavaScript 的语法扩展(eXtension),也在很多地方称之为 JavaScript XML,因为看起就是一段 XML 语法。

它用于描述我们的 UI 界面,并且可以和 JavaScript 融合在一起使用。

1. 为什么要有 jsx?

JS 创建虚拟 DOM

jsx
// 1.创建虚拟DOM, 创建嵌套格式的dom
const VDOM = React.createElement('h1',{id:'title'},React.createElement('span',{},'hello,React'))
// 2.渲染虚拟DOM到页面
ReactDOM.render(VDOM,document.querySelector('.test'))

Jsx 创建虚拟 DOM

jsx
// 1.创建虚拟DOM
const VDOM = (  /* 此处一定不要写引号,因为不是字符串 */
    <h1 id="title">
        <span>Hello,React</span>
    </h1>
)
// 2.渲染虚拟DOM到页面
ReactDOM.render(VDOM,document.querySelector('.test'))

可见,js 的写法并不是很好的。常用 jsx 来写,毕竟 JSX 更符合书写的习惯。

2. jsx 语法

1)基础语法

元素:JSX 元素是构成 React 应用的基本单位。它们看起来像 HTML 标签,但实际上是 JavaScript 函数的调用。

定义虚拟 DOM,不能使用 ""

不能有多个根标签,只能有一个根标签。

标签必须闭合,自闭合也行。

子元素:如果一个 JSX 标签是空的,可以使用 /> 来关闭它。如果想在其中添加子元素,可以使用 </xxx> 来关闭它。

jsx
const element = <img src={user.avatarUrl} />;
const element = <div>Hello, {name}!</div>

表达式:可以在 JSX 中嵌入任何有效的 JavaScript 表达式。只需将它们包裹在花括号中即可。

jsx
const name = 'Josh Perez';
const element = <h1>Hello, {name.toUpperCase()}</h1>;

属性:可以使用字符串字面量或花括号中的 JavaScript 表达式为 JSX 元素设置属性。

jsx
const element = <div tabIndex="0"></div>;
const element = <img src={user.avatarUrl}></img>;

样式

样式的类名指定不能使用 class,使用 className

内联样式要使用 {{}} 包裹。

jsx
style={{color:'skyblue',fontSize:'24px'}}

组件

如果小写字母开头,就将标签转化为 html 同名元素,如果 html 中无该标签对应的元素,就报错;如果是大写字母开头,react 就去渲染对应的组件,如果没有就报错。

jsx
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

// 组件可以是函数或类,它们的名称必须以大写字母开头。
const element = <Welcome name="yxts" />;

注释:可以在 JSX 中添加注释,但需要将它们包裹在花括号和 /* */ 中。

jsx
const element = (
  <div>
    {/* This is a comment */}
    Hello, {name}!
  </div>
);

数组

JSX 允许在模板中插入数组,数组自动展开全部成员。

jsx
var arr = [
  <h1>历史消费记录汇总</h1>,
  <h2>本月消费记录</h2>,
];
ReactDOM.render(
  <div>{arr}</div>,
  document.getElementById('example')
);

如果 {} 中是对象,那么就会报错。也就是说,React 默认无法遍历对象。

2)小练习

循环遍历数组,使用无序列表展示到页面上。

jsx
const data = ['Angular','React','Vue']
const VDOM = (
    <div>
        <ul>
            {
                data.map((item,index)=>{
                    return <li key={index}>{item}</li>
                })
            }
        </ul>
    </div>
)
ReactDOM.render(VDOM,document.querySelector('.test'))

3. JSX 本质

jsx 仅仅只是 React.createElement(component, props, ...children) 函数的语法糖。

所有的 jsx 最终都会被转换成 React.createElement 的函数调用。通过 React.createElement 最终创建出来一个 ReactElement 对象。

这个 ReactElement 对象是什么作用呢? React 为什么要创建它呢?

原因是 React 利用 ReactElement 对象组成了一个 JavaScript 的对象树。JavaScript 的对象树就是虚拟 DOM(Virtual DOM)。

可以通过 ReactDOM.render 让虚拟 DOM 和真实 DOM 同步起来,这个过程中叫做协调(Reconciliation)。

虚拟 DOM 的存在使得从命令式变为声明式编程。

第二章:面向组件编程基础

一、快速入门

1. 函数式组件

jsx
// 1.先创建函数,函数可以有参数,也可以没有,但是必须要有返回值,返回一个虚拟DOM
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}
// 2.进行渲染
ReactDOM.Render(<Welcom name = "yxts" />,document.getElementById("div"));

上面的代码经历了以下几步:

① 我们调用 ReactDOM.render() 函数,并传入 <Welcome name="yxts" /> 作为参数。

② React 调用 Welcome 组件,并将 {name: 'yxts'} 作为 props 传入。

③ Welcome 组件将 Hello, yxts 元素作为返回值。

④ React DOM 将 DOM 高效地更新为 Hello, yxts

2. Class 组件

jsx
// 必须继承React.Component
// 然后重写Render()方法,该方法一定要有返回值,返回一个虚拟DOM
class Welcome extends React.Component {
  render() {
    return <h1>Hello, yxts</h1>;
  }
}
// 渲染 【这个跟之前也是一样的】
ReactDOM.Render(<Welcom />,document.getElementById("div"));

执行过程:

① React 解析组件标签,找到相应的组件。

② 发现组件是类定义的,随后 new 出来的类的实例,并通过该实例调用到原型上的 render 方法。

③ 将 render 返回的虚拟 DOM 转化为真实的 DOM,随后呈现在页面中。

二、组件实例的三大属性

1. state

都说 React 是一个状态机,体现是什么地方呢,就是体现在 state 上,通过与用户的交互,实现不同的状态,然后去渲染 UI,这样就让用户的数据和界面保持一致了。state 是组件的私有属性。

在 React 中,更新组件的 state,结果就会重新渲染用户界面(不需要操作 DOM)。一句话就是说,用户的界面会随着状态的改变而改变。

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

1)案例

页面显示【今天天气很炎热】,鼠标点击文字的时候,页面更改为【今天天气很凉爽】。

2)实现
[1] 原始版本
jsx
<body>
    <!-- 准备好容器 -->
    <div id="test"></div>
</body>

<!-- 引入依赖 ,引入的时候,必须就按照这个步骤 -->
<script src="../js/react.development.js" type="text/javascript"></script>
<script src="../js/react-dom.development.js" type="text/javascript"></script>
<script src="../js/babel.min.js"></script>

<!-- 这里使用了js来创建虚拟DOM -->
<script type="text/babel">
        {/* 1.创建组件 */}
        class St extends React.Component{
            constructor(props){
                // 在构造器中通过 this.prop 来访问,必须添加这个,否则可以省略
                super(props);
                // 先给state赋值
                this.state = {isHot:true};
                // 找到原型的dem,根据dem函数创建了一个dem1的函数,并且将实例对象的this赋值过去
                this.dem = this.dem.bind(this);
            }
            // render会调用1+n次【1就是初始化的时候调用的,n就是每一次修改state的时候调用的】
            render(){ // 这个This也是实例对象
                // 如果加dem(),就是将函数的回调值放入这个地方
                // this.dem这里面加入this,并不是调用,只不过是找到了dem这个函数,在调用的时候相当于直接调用,并不是实例对象的调用
                return <h1 onClick = {this.dem}>今天天气很{this.state.isHot?"炎热":"凉爽"}</h1>    
            }
            // 通过state的实例调用dem的时候,this就是实例对象
            dem(){
                const state =  this.state.isHot;
                // 状态中的属性不能直接进行更改,需要借助API
                // this.state.isHot = !isHot; 错误
                // 必须使用setState对其进行修改,并且这是一个合并
                this.setState({isHot:!state});
            }
        }
        {/* 2.渲染,如果有多个渲染同一个容器,后面的会将前面的覆盖掉 */}
        ReactDOM.render(<St />,document.getElementById("test"));
</script>

需要注意的是:

  • 组件的构造函数,必须要传递一个 props 参数。

  • 特别关注 this【重点】,类中所有的方法局部都开启了严格模式,如果直接进行调用,this 就是 undefined。

  • 想要改变 state,需要使用 setState 进行修改。如果是修改 state 的部分属性,是不会影响到其他属性的值的,这个只是合并,并不是覆盖。

  • this.setState(),该方法接收两种参数:对象或函数。

    • 对象:即想要修改的 state。

    • 函数:接收两个函数。第一个函数接受两个参数,第一个是当前 state,第二个是当前 props,该函数返回一个对象,和直接传递对象参数是一样的,就是要修改的 state;第二个函数参数是 state 改变后触发的回调。

[2] 简化版本

state 的赋值可以不在构造函数中进行;事件回调函数使用了箭头函数,将 this 进行改变。

jsx
<body>
    <!-- 准备好容器 -->
    <div id="test"></div>
</body>

<!-- 引入依赖 ,引入的时候,必须就按照这个步骤 -->
<script src="../js/react.development.js" type="text/javascript"></script>
<script src="../js/react-dom.development.js" type="text/javascript"></script>
<script src="../js/babel.min.js"></script>

<script type="text/babel">
        class St extends React.Component{
            // 可以直接对其进行赋值
            state = {isHot:true};
            render(){ // 这个This也是实例对象
                return <h1 onClick = {this.dem}>今天天气很{this.state.isHot?"炎热":"凉爽"}</h1>    
                // 或者使用{()=>this.dem()}也是可以的
            }
            // 箭头函数 [自定义方法--->要用赋值语句的形式+箭头函数]
            dem = () =>{
                console.log(this);
                const state =  this.state.isHot;
                this.setState({isHot:!state});
            }
        }
        ReactDOM.render(<St />,document.getElementById("test"));       
</script>
3)总结

this 绑定问题?

方案一:bind 给事件回调函数显示绑定 this。

方案二:使用 ES6 class fields 语法。

方案三:事件监听时传入箭头函数(个人推荐)。

事件参数传递

如果想要在调用方法的时候传递参数,有三种方法:

jsx
{/* 第一种方法 */}
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
{/* 第二种方法 */}
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
{/* 第三种方法:函数柯里化,见事件处理章节 */}

上述前两种方式是等价的,分别通过箭头函数Function.prototype.bind 来实现。

在这两种情况下,React 的事件对象 e 会被作为第二个参数传递。如果通过箭头函数的方式,事件对象必须显式的进行传递,而通过 bind 的方式,事件对象以及更多的参数将会被隐式的进行传递。

2. props

Props 主要用来传递数据,比如组件之间进行传值。

1)基本使用
jsx
<body>
    <div id = "div"></div>
</body>

<script type="text/babel">
    class Person extends React.Component{
        render(){
            return (
                <ul>
                    {/* 接受数据并显示 */}
                    <li>{this.props.name}</li>
                    <li>{this.props.age}</li>
                    <li>{this.props.sex}</li>
                </ul>
            )
        }
    }
    {/* 传递数据 */}
    ReactDOM.render(<Person name="tom" age = "41" sex="男"/>,document.getElementById("div"));
</script>
2)批量传递

如果传递的数据是一个对象,可以更加简便的使用。

jsx
<script type="text/babel">
    class Person extends React.Component{
        render(){
            return (
                <ul>
                    <li>{this.props.name}</li>
                    <li>{this.props.age}</li>
                    <li>{this.props.sex}</li>
                </ul>
            )
        }
    }
    const p = {name:"张三",age:"18",sex:"女"}
   ReactDOM.render(<Person {...p}/>,document.getElementById("div"));
</script>

注意:{...P} 并不是 ES6 的语法,一毛钱关系都没有。之所以 props 传递一个对象,是因为 babel + react 使得 {..p} 可以展开对象,但是只有在标签中才能使用。

3)对 props 限制

很多时候想要传递的参数进行相应的限制,比如:限制传递参数的类型,参数的默认值等等。

react 对此提供了相应的解决方法:

  • propTypes:类型检查,还可以限制不能为空。
  • defaultProps:默认值。

React 15.xxx 版本在 React 原型上有 PropTypes 属性,但在 React 16.xxx 版本没有了,需要引入 prop-types.js,一引入这个文件,在全局就多了 PropTypes 对象。

jsx
<script type="text/babel">
    class Person extends React.Component{
        render(){
            // props是只读的
            return (
                <ul>
                    <li>{this.props.name}</li>
                    <li>{this.props.age}</li>
                    <li>{this.props.sex}</li>
                </ul>
            )
        }
        {/* 第一种限制类型的书写方式 */}
        // 对组件的属性对其进行限制
        static propTypes = {
            name: PropTypes.string.isRequired, // 限定name是string类型,并且必须要传递
            sex: PropTypes.string,  // 限定sex是string类型
            speak: PropTypes.func   // 限定speak是function类型
        }
        // 指定默认的标签属性
        static defaultProps = {
            sex: "不男不女",
            age: 18
        }
        
    }
    {/* 第二种限制类型的书写方式
      Person.propTypes = {
          name: React.PropTypes.string.isRequired, // 限定name是string类型,并且必须要传递
          sex: React.PropTypes.string,  // 限定sex是string类型
          speak: React.PropTypes.func   // 限定speak是function类型
      }
      Person.defaultProps = {
          sex: "不男不女",
          age: 18
      }
    */}
    {/* {14}就代表的是数值 */}
    ReactDOM.render(<Person name="yxts" age={14} speak="8"/>,document.getElementById("div"));
    
    function speak(){
        console.log("这个是一个函数")
    }
</script>
</html>
4)函数式组件的使用

函数在使用 props 的时候,是作为参数进行使用的。

jsx
function Person(props){
    return (
        <ul>
            <li>{props.name}</li>
            <li>{props.age}</li>
            <li>{props.sex}</li>
        </ul>
    )
}

3. Refs

Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。

Refs 主要提供了三种方式:

1)字符串形式

想要获取到一个 DOM 节点,可以直接在这个节点上添加 ref 属性。然后利用 refs 属性进行获取该节点。

案例:给需要的节点添加 ref 属性,此时该实例对象的 refs 上就会有这个值。就可以利用实例对象的 refs 获取已经添加节点的值。

jsx
<input ref="input1" type="text" placeholder="点击弹出" />

inputBlur = () => {
    let {input1} = this.refs
    alert(input1.value)
}
2)回调形式

回调形式会在 ref 属性中添加一个回调函数。将该 DOM 作为参数传递过去。

如:ref 里面就是一个回调函数,self 就是该 input 标签。然后在将该 DOM 元素赋值给实例对象中的一个属性。

jsx
<input ref={self =>{ this.input1 = self;console.log(self)}}  placeholder="点击触发事件" />

也可以将函数提取出来,在 ref 中进行调用。

jsx
isRef = (self) =>{
    this.input1 = self;
    console.log(self)
}

<input ref={this.isRef} type="text" placeholder="点击弹出" />
3)API 形式

React 其实已经给我们提供了一个相应的 API(React.createRef()),它会自动的将该 DOM 元素放入实例对象中。

如下:依旧先在 DOM 元素中添加一个 ref 元素。

jsx
{/* <input ref={this.容器名称} type="text" placeholder="点击弹出" /> */}
<input ref={this.MyRef1} type="text" placeholder="点击弹出" />
<input ref={this.MyRef2} type="text" placeholder="点击弹出" />

通过 API,创建 React 的容器,相当于省略了回调的中间环节。但是这个容器是专人专用的,所以每一个 ref 都需要创建这个。该 API 会将 DOM 元素赋值给实例对象的名称为容器名称的属性 current(这个 current 是固定的)。

jsx
{/* 容器名称 = React.createRef() */}
MyRef1 = React.createRef();
MyRef2 = React.createRef();

然后就可以使用了。

jsx
btnOnClick = () =>{
    // 创建之后,将自身节点,传入current中
    console.log(this.MyRef1.current.value);
}

官方提示我们不要过度的使用 ref,如果发生时间的元素刚好是需要操作的元素,就可以使用事件去替代。

第三章:基础语法

一、事件处理

1. 事件绑定

React 使用的是自定义事件,而不是原生的 DOM 事件。通过 onXxx 属性指定某个事件发生时执行的函数。

React 的事件是通过事件委托方式处理的(为了更加的高效)。

可以通过事件的 event.target 获取到发生该事件的 DOM 元素对象,以减少 refs 的使用。

jsx
<input onChange={this.saveName} type="text" name="username"/>

saveName = (event) => {
    this.setState({name:event.target.value})
}

2. 受控和非受控组件

1)非受控组件

非受控组件其实就是表单元素的值不会更新 state。输入数据都是现用现取的。

如下:并没有使用 state 来控制属性,使用的是事件来控制表单的属性值。

jsx
class Login extends React.Component{
    login = (event) => {
        event.preventDefault(); // 阻止表单提交
        console.log(this.name.value);
        console.log(this.pwd.value);
    }
    
    render() {
        return (
            <form action="http://www.baidu.com" onSubmit={this.login}>
                用户名:<input ref = {self => this.name =self } type = "text" name ="username"/>
                密码:<input ref = {self => this.pwd =self } type = "password" name ="password"/>
                <button>登录</button>
            </form>
        )
    }
}
2)受控组件

使 React 的 state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。

jsx
saveName = (event) =>{
    this.setState({name:event.target.value});
}

savePwd = (event) => {
    this.setState({pwd:event.target.value});
}

render() {
    return (
        <form action="http://www.baidu.com" onSubmit={this.login}>
            用户名:<input value={this.state.name} onChange={this.saveName} type = "text" />
            密码<input value={this.state.pwd} onChange={this.savePwd} type = "password"/>
            <button>登录</button>
        </form>
    )
}

由于在表单元素上设置了 value 属性,因此显示的值将始终为 this.state.value,这使得 React 的 state 成为唯一数据源。由于 onchange 在每次按键时都会执行并更新 React 的 state,因此显示的值将随着用户输入而更新。

对于受控组件来说,输入的值始终由 React 的 state 驱动。

使用珂里化函数改写

jsx
class Login extends React.Component{
    
    state = {name:"",pwd:""};
    
    // 返回一个函数
    saveType = (type) =>{
        return (event) => {
            this.setState({[type]:event.target.value});
        }
    }
    
    // 因为事件中必须是一个函数,所以返回的也是一个函数,这样就符合规范了
    render() {
        return (
            <form>
      		    <input onChange = {this.saveType('name')} type = "text"/>
                <button>登录</button>
            </form>
        )
    }
}

ReactDOM.render(<Login />,document.getElementById("div"));

除了上面使用珂里化函数实现多个表单同一个函数,react 官方给出了通过表单控件的 name 属性实现多个表单同一个函数。

jsx
import React, { PureComponent } from 'react'

export class App extends PureComponent {

  constructor(props) {
    super(props)

    this.state = {
      username: "",
      password: ""
    }
  }

  handleSubmitClick(event) {
    // 1.阻止默认的行为
    event.preventDefault()

    // 2.获取到所有的表单数据, 对数据进行组件
    console.log("获取所有的输入内容")
    console.log(this.state.username, this.state.password)

    // 3.以网络请求的方式, 将数据传递给服务器(ajax/fetch/axios)
  }

  // handleUsernameChange(event) {
  //   this.setState({ username: event.target.value })
  // }

  // handlePasswordChange(event) {
  //   this.setState({ password: event.target.value })
  // }

  handleInputChange(event) {
    this.setState({
      [event.target.name]: event.target.value
    })
  }

  render() {
    const { username, password } = this.state

    return (
      <div>
        <form onSubmit={e => this.handleSubmitClick(e)}>
          {/* 1.用户名和密码 */}
          <label htmlFor="username">
            用户: 
            <input 
              id='username' 
              type="text" 
              name='username' 
              value={username} 
              onChange={e => this.handleInputChange(e)}
            />
          </label>
          <label htmlFor="password">
            密码: 
            <input 
              id='password' 
              type="password" 
              name='password' 
              value={password} 
              onChange={e => this.handleInputChange(e)}
            />
          </label>

          <button type='submit'>注册</button>
        </form>
      </div>
    )
  }
}

export default App

二、条件渲染

方式一:条件判断语句

方式二:三元运算符

方式三:与运算符

方法四:v-show 的效果

jsx
{/* v-show的效果 */}
<h2 style={{display: isShow ? 'block': 'none'}}>哈哈哈哈</h2>

changeShow() {
	this.setState({ isShow: !this.state.isShow })
}

三、列表渲染

如何展示列表呢?

在 React 中,展示列表最多的方式就是使用数组的 map 高阶函数。

很多时候我们在展示一个数组中的数据之前,需要先对它进行一些处理:

  • 比如过滤掉一些内容:filter 函数。

  • 比如截取数组中的一部分内容:slice 函数。

最好在添加一个 key 属性,用于 diff 算法。

四、插槽

方案一:this.props.children

如果标签体传递一个标签,那么 children 就是一个对象(这个标签对象);如果标签体传递多个标签,那么 children 就是数组,数组里放的都是这一个个标签对象。

方案二(推荐):this.props

采用 render props 技术,就可以像组件内部动态传入带有内容的结构。

当我们在一个组件标签中填写内容时,这个内容会被定义为 children props,我们可以通过 this.props.children 来获取。

例如:

jsx
<A>hello</A>

而我们所说的 render props 就是在组件标签中传入一个 render 方法,又因为属于 props,因而被叫做了 render props。

jsx
<A render={(name) => <C name={name} />} />

你可以把 render 看作是 props,只是它有特殊作用,当然它也可以用其他名字来命名。

在上面的代码中,我们需要在 A 组件中预留出 C 组件渲染的位置,在需要的位置上加上 {this.props.render(name)}

那我们在 C 组件中,如何接收 A 组件传递的 name 值呢?通过 this.props.name 的方式。

作用域插槽

jsx
getTabItem(item) {
	if (item === "流行") {
		return <span>{item}</span>
	} else if (item === "新款") {
		return <button>{item}</button>
	} else {
		return <i>{item}</i>
	}
}

render() {
	const { titles, tabIndex } = this.state

	return (
		<div className='app'>
			<TabControl 
				titles={titles} 
				tabClick={i => this.tabClick(i)}
				itemType={item => this.getTabItem(item)}
			/>
			<h1>{titles[tabIndex]}</h1>
		</div>
	)
}
jsx
render() {
	const { titles, itemType } = this.props
	const { currentIndex } = this.state

	return (
		<div className='tab-control'>
			{
				titles.map((item, index) => {
					return (
						<div 
							className={`item ${index === currentIndex?'active':''}`} 
							key={item}
							onClick={e => this.itemClick(index)}
						>
							{/* <span className='text'>{item}</span> */}
							{itemType(item)}
						</div>
					)
				})
			}
		</div>
	)
}

五、搜集表单数据

ElementValue propertyChange callbackNew value in the callback
<input type="text" />value="string"onChangeevent.target.value
<input type="checkbox" />checked=onChangeevent.target.checked
<input type=”radio" />checked=onChangeevent.target.checked
<textarea />value="string"onChangeevent.target.value
<select />value="option value"onChangeevent.target.value

1. Checkbox 单选

jsx
constructor(props) {
	super(props)

	this.state = {
		isAgree: false
	}
}

handleAgreeChange(event) {
	this.setState({ isAgree: event.target.checked })
}

{/* checkbox单选 */}
<label htmlFor="agree">
	<input
		id='agree'
		type="checkbox"
		checked={isAgree}
		onChange={e => this.handleAgreeChange(e)}
	/>
	同意协议
</label>

2. Checkbox 多选

jsx
constructor(props) {
	super(props)

	this.state = {
		hobbies: [
			{ value: "sing", text: "唱", isChecked: false },
			{ value: "dance", text: "跳", isChecked: false },
			{ value: "rap", text: "rap", isChecked: false }
		]
	}
}

handleHobbiesChange(event, index) {
	const hobbies = [...this.state.hobbies]
	hobbies[index].isChecked = event.target.checked
	this.setState({ hobbies })
}

{/* checkbox多选 */}
<div>
	您的爱好:
	{
		hobbies.map((item, index) => {
			return (
				<label htmlFor={item.value} key={item.value}>
					<input
						type="checkbox"
						id={item.value}
						checked={item.isChecked}
						onChange={e => this.handleHobbiesChange(e, index)}
					/>
					<span>{item.text}</span>
				</label>
			)
		})
	}
</div>

3. Select 单选

jsx
constructor(props) {
	super(props)

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

handleFruitChange(event) {
	this.setState({ fruit: event.target.value })
}

{/* select */}
<select value={fruit} onChange={e => this.handleFruitChange(e)} multiple>
	<option value="apple">苹果</option>
	<option value="orange">橘子</option>
	<option value="banana">香蕉</option>
</select>

4. Select 多选

event.target.selectedOptions 是一个 HTML DOM 属性,它返回一个 HTMLCollection 对象,该对象包含了用户在一个 <select> 元素中选择的所有 <option> 元素。

这个属性通常在处理 <select multiple> 元素的 change 事件时使用,因为用户可以在这种元素中选择多个选项。

jsx
constructor(props) {
	super(props)

	this.state = {
		fruit: ["orange"]
	}
}

handleFruitChange(event) {
	const options = Array.from(event.target.selectedOptions)
	const values = options.map(item => item.value)
	this.setState({ fruit: values })

	// 额外补充: Array.from(可迭代对象)
	// Array.from(arguments)
	const values2 = Array.from(event.target.selectedOptions, item => item.value)
	console.log(values2)
}

{/* select */}
<select value={fruit} onChange={e => this.handleFruitChange(e)} multiple>
	<option value="apple">苹果</option>
	<option value="orange">橘子</option>
	<option value="banana">香蕉</option>
</select>

第四章:生命周期

一、旧版

React v16.3 版本及以前。

组件从创建到销毁,会经过一些特定的阶段。我们在定义组件的时候,会在特定的生命周期回调函数中做特定的工作。因此,React 组件中包含一系列钩子函数(生命周期回调函数),会在特定的时刻调用。

如下图是旧生命周期的流程图:

React 生命周期主要包括三个阶段:初始化阶段,更新阶段,卸载阶段。

1. 初始化阶段

ReactDOM.render() 触发 — 页面初次渲染。

1)constructor 执行

constructor 在组件初始化的时候只会执行一次。

通常它用于做这两件事:

① 初始化函数内部 state。

② 绑定函数。

jsx
constructor(props) {
    super(props)
    console.log('进入构造器');
    this.state = { count: 0 }
    this.handleClick = this.handleClick.bind(this);
}

现在通常不会使用 constructor,而是改用类加箭头函数的方法,来替代 constructor。

例如,可以这样初始化 state。

jsx
state = {
	count: 0
}
handleClick = () => {
  // this'指的是组件实例
  console.log(this.state.count);
}
2)componentWillMount 执行

该方法只在挂载的时候调用一次,表示组件将要被挂载,并且在 render 方法之前调用。

这个方法在 React 17 版本中将要被废弃,官方解释是在 React 异步机制下,如果滥用这个钩子可能会有 Bug。

如果存在 getDerivedStateFromProps 和 getSnapshotBeforeUpdate 就不会执行生命周期 componentWillMount。

3)render 执行

render() 方法是组件中必须实现的方法,用于渲染 DOM,但是它不会真正的操作 DOM,它的作用是把需要的东西返回出去。

实现渲染 DOM 操作的是 ReactDOM.render()

注意:避免在 render 中使用 setState,否则会死循环。

4)componentDidMount 执行

componentDidMount 的执行意味着初始化挂载操作已经基本完成,它主要用于组件挂载完成后做某些操作(例如:开启定时器、发送网络请求、订阅消息)。

这个挂载完成指的是:组件已经插入到 DOM tree 上了。

2. 更新阶段

由组件内部 this.setState() 或父组件重新 render 触发。

  • shouldComponentUpdate():可以利用这个方法决定组件是否需要重新渲染。如果 shouldComponentUpdate 返回 false,则 render() 会被跳过,直到下一次 state 改变。默认情况下,这个方法返回 true。

  • componentWillUpdate():在 render() 之前被调用,当新的 props 或 state 被接收时。注意,不能在这个方法中调用 this.setState()。这个方法在 React 17 版本已被废弃,建议使用 getSnapshotBeforeUpdate 或 componentDidUpdate。

  • render():必须使用的一个。

  • componentDidUpdate():在更新发生后立即被调用。这是在更新后可能需要操作 DOM 的地方。注意,此方法在首次渲染时不会被调用。

3. 卸载阶段

由 ReactDOM.unmountComponentAtNode() 触发。

componentWillUnmount() ==> 一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息(常用)。

二、新版

1. 增废哪些?

在 React 17 版本中,componentWillMount、componentWillReceiveProps、componentWillUpdate 生命周期方法已被废弃。

这些方法在新的代码中不应再使用,因为在未来的 React 版本中可能会被完全移除。

取而代之的是新的生命周期方法:

① getDerivedStateFromProps:用于替换 componentWillReceiveProps,这是一个静态方法,用于在 props 改变时更新 state。

② getSnapshotBeforeUpdate:用于替换 componentWillUpdate,在 DOM 更新前被调用,返回值将作为 componentDidUpdate 的第三个参数。

③ componentDidMount,componentDidUpdate 和 componentWillUnmount 没有变化,仍然可以使用。

此外,React 也引入了 Hooks,这是一种新的方式来使用 state 和其他 React 特性,而不必写 class。

static getDerivedStateFromProps

这个是 React 新版本中新增的 2 个钩子之一,很少用。

getDerivedStateFromProps 在初始化和更新中都会被调用,并且在 render 方法之前调用,它返回一个对象用来更新 state。

getDerivedStateFromProps 是类上直接绑定的静态(static)方法,它接收两个参数 props 和 state。

props 是即将要替代 state 的值,而 state 是当前未替代前的值。

注意:state 的值在任何时候都取决于传入的 props,不会再改变。

如下:

jsx
class Count extends React.Component {
    // 构造器
    constructor(props) {
        console.log('Count---constructor')
        super(props)
        // 初始化状态
        this.state = { count: 0 }
    }
    // 从props中得到一个派生的状态
    // 若state 的值在任何时候都取决于 props,那么可以使用
    static getDerivedStateFromProps(props,state) {
        console.log('getDerivedStateFromProps', props,state)
        // return {count:199}
        return props
    }
}

// 渲染组件
ReactDOM.render(< Count count='199' />, document.getElementById('test'))

getDerivedStateFromProps() 可以接收 props 参数。给 Count 组件传递 props(<Count count='199' />),然后 getDerivedStateFromProps(props) 可以接收到,返回 props(return props),所以从 props 中得到一个派生的状态(不是自己写的),即 state 的值在任何时候都取决于 props,无论是初始化,或者修改都不起作用。

getSnapshotBeforeUpdate

在最近一次的渲染输出之前被提交之前调用,也就是即将挂载时调用。

相当于淘宝购物的快照,会保留下单前的商品内容,在 React 中就相当于是【即将更新前的状态】。

补充一下:componentDidUpdate(prevProps, prevState, snapshot)

该生命周期函数,可以有三个参数:原始传过来的参数、最开始的状态、getSnapshotBeforeUpdate 传递的值。

关于更多关于生命周期的介绍,可以参考官方文档:React.Component

它可以使组件在 DOM 真正更新之前捕获一些信息(例如滚动位置),此生命周期返回的任何值都会作为参数传递给 componentDidUpdate()。如不需要传递任何值,那么请返回 null。

案例:在一个区域内,定时的输出一行话,如果内容大小超过了区域大小,就出现滚动条,但是内容不进行移动。

如上面的动图:区域内部的内容展现没有变化,但是可以看见滚动条在变化,也就是说上面依旧有内容在输出,只不过不在这个区域内部展现。

① 首先先实现定时输出内容

可以使用 state 状态,改变新闻后面的值,但是为了同时显示这些内容,应该为 state 的属性定义一个数组。并在创建组件之后开启一个定时器,不断的进行更新 state。

jsx
class New extends React.Component{
    state = {num:[]};
    
    // 在组件创建之后,开启一个定时任务
    componentDidMount(){
        setInterval(()=>{
            let {num} = this.state;
            const news = (num.length+1);
            this.setState({num:[news,...num]});
        },2000);
    }
    
    render(){
        return (
            <div ref = "list" className = "list">
                {
                    this.state.num.map((n,index)=>{
                        return <div className="news" key={index} >新闻{n}</div>
                    })
                }
            </div>
        )
    }
}

ReactDOM.render(<New />,document.getElementById("div"));

② 接下来就是控制滚动条了

要实现鼠标控制滚动条滚动到某一位置,界面不动,不会自动跳转到最顶部新出现的新闻上去。

这一功能,必须用到 getSnapshotBeforeUpdate 这个钩子,在更新之前拿到内容区的高度,传给 componentDidUpdate 这个钩子作为第三个参数。

最关键的一步,在 componentDidUpdate 这个钩子里实现上述这个功能:动态地调整这个 this.refs.list.scrollTop 往上窜地高度。拿到更新之后的内容区高度,减去更新之前的内容区高度,加上原先计算前的 scrollTop 就能设置新加的元素往上窜地高度,从而实现上述功能。

jsx
getSnapshotBeforeUpdate(){
	return this.refs.list.scrollHeight;
}

componentDidUpdate(preProps,preState,height){
	this.refs.list.scrollTop += (this.refs.list.scrollHeight - height);
}

这样就实现了这个功能。

2. 新生命周期阶段

  • 初始化阶段:由 ReactDOM.render() 触发 — 初次渲染

    constructor()

    getDerivedStateFromProps()

    render()

    componentDidMount()

  • 更新阶段:由组件内部 this.setSate() 或父组件重新 render 触发

    getDerivedStateFromProps()

    shouldComponentUpdate()

    render()

    getSnapshotBeforeUpdate()

    componentDidUpdate()

  • 卸载组件:由 ReactDOM.unmountComponentAtNode() 触发

    componentWillUnmount()

重要的三个钩子

① render:初始化渲染或更新渲染调用。

② componentDidMount:开启监听,发送 ajax 请求。

③ componentWillUnmount:做一些收尾工作,如:清理定时器。

即将废弃的钩子

① componentWillMount

② componentWillReceiveProps

③ componentWillUpdate

在 v16.3 版本及以后使用需要加上 UNSAFE_ 前缀才能使用,否则会出现警告。以后可能会被彻底废弃,不建议使用。

Released under the MIT License.