React.js快速进坑.md 13 KB

React.js快速进坑

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

1. Vue.js 对比 React

都是构建UI的框架,但关注点不同:

  • Vue.js 对 HTML 进行了扩展。各种指令
  • React 对 JavaScript 进行了扩展,新增一个语法-JSX

因此,Vue在构建页面上更便捷,而React在构建页面时可以使用全部的JS能力。

2. 安装

与Vue.js一样,有CDN链接;也可以通过npm独立安装;当然也有对应的脚手架工具。

为了更快下载必要js文件,将CDN链接上的js下载本地。

  • babel: 转译jsx语法,即将jsx 转换成 js对象(虚拟DOM)
  • React核心库:提供核心功能。比如如何定义组件,Hook等等
  • React DOM库:提供与DOM相关的功能。比如 将React组件 渲染到页面中,输出信息...

2.1 Hello, world

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Hello</title>
    <script src="../babel.min.js"></script>
    <script src="../react.development.js"></script>
    <script src="../react-dom.development.js"></script>
  </head>
  <body>
    <div id="root"></div>
    <!-- 这个script不能是普通的脚本 -->
    <!-- 通过type属性告诉babel这里的代码 是需要它转译 -->
    <script type="text/babel">
      // 0 创建一个简单 UI:
      const element = <h1>Hello, world.</h1>; // 这就是jsx
      // 创建root实例,提供一个render方法 可以将构建的UI 渲染到页面中
      // 参数 为 一个DOM对象
      // 1. 获取容器的dom对象
      const containerEle = document.getElementById('root');
      // 2. 创建root实例,并指定对应容器DOM
      const root = ReactDOM.createRoot(containerEle);
      // 3. 将 element 渲染到 指定容器DOM下
      root.render(element);
    </script>
  </body>
</html>

3. JSX

形似 <h1>Hello, World.</h1>这样的语法,看着 既不是 HTML 标签, 也不是 JS中数据类型。它就是 jsx.

3.1 渲染数据

在React中,可以使用{}将一个JS表达式的值 绑定到 JSX 中。

let username = 'guoguo';
// 在React中可以使用 "{}" 去 在jsx中渲染数据
// 在"{js表达式}"中,编写的是 JS表达式。类似与 Vue的插值语法"{{}}"
let element = <h3>Hello, {username}.</h3>; // <h3>Hello, daxia.</h3>

3.2 绑定属性

在开发UI时,jsx的操作会像HTML标签那样去指定各种属性。

由于 JSX 最终会被 babel 转译成 JS对象。因此JSX的属性命名规则要遵从JS规范。

  • 不能使用js中关键字后者保留字
  • 属性名字 推荐使用 驼峰命名法 camelCase

实现时注意以下几点:

  1. 那些是关键字或保留字的HTML属性需要改名:for => htmlFor; class => className
  2. 那些包含多个单词的HTML属性需要转换成驼峰命名:rowspan =>rowSpan; colspan => colSpan; ...
  3. 剩下那些就无需改变,直接可以使用。

    // let element = (
    //   <h3 id="hello" className="h3-red">
    //     Hello, React.
    //   </h3>
    // );
    /**
    *! 1. jsx元素必须闭合
    *! 2. 在jsx中注释内容的话 需要使用 绑定语法
    */
    let element = (
    <div>
    <label htmlFor="name">姓名:</label>
    {/* jsx元素必须闭合*/}
    <input id="name" type="text" placeholder="输入姓名..." tabIndex="1" />
    <br />
    <label htmlFor="xueli">学历:</label>
    <input
      id="xueli"
      type="text"
      placeholder="输入学历..."
      tabIndex="3"
    />
    <input type="text" placeholder="干扰..." tabIndex="2" />
    </div>
    );
    
    ReactDOM.createRoot(document.getElementById('root')).render(element);
    

4. 事件处理

所有UI界面都需要通过事件来完成交互的。

不同与Vue,有v-on指令来处理事件,react是通过jsx相对应的事件属性来实现的

React在处理事件时,和原生DOM对象类似。可以通过对应事件相关属性来注册监听器。

不同的是:

  1. 原生DOM事件属性 都是 全小写的;而React是 基于原生事件属性转换后的驼峰命名。
  2. 原生DOM事件绑定的值类型为 string;而React JSX 事件绑定的值类型为 function。

    <!-- 原生事件注册 -->
    <button id="btn">点我试试</button>
    <script>
    // 比如注册点击事件
    document.getElementById('btn').onclick = function () {
    alert('你敢点我???');
    };
    </script>
    
    // React中的实现
    function handleClick() {
    alert('你敢点我???');
    }
    const element = <button onClick={handleClick}>点我试试</button>;
    
    ReactDOM.createRoot(document.querySelector('#root')).render(element);
    

4.1 事件对象

在实际开发中,当某一事发生后开发者需要根据当前事件获取相关的信息数据。此时就需要找到事件对象。不同的事件,会创建不同事件对象,并且不同的事件对象包含的数据也不尽相同。

那么怎么获取 事件对象 呢?默认情况下,JSX中注册所有监听器函数都会接收一个 事件对象 作为 唯一入参。因此在需要时在事件监听器函数中定义形参接收即可。

  // 事件监听的第一个参数 就是 事件对象
  function handleClick(e) {
    console.log(e);
    alert('你敢点我???');
  }

  function handleChage(e) {
    // 每次文本改后将其打印出来
    console.log('changed:', e.target.value);
  }

  function handleSubmit(e) {
    e.preventDefault(); // 取消默认行为
    alert('submited.');
  }

  const element = (
    <div>
      {/**1 点击事件 **/}
      <button onClick={handleClick}>点我试试</button>
      {/**2 change事件 **/}
      {/* input元素的change事件 和原生的input事件 是一样的 */}
      <input type="text" onChange={handleChage} />

      {/**3 submit事件 **/}
      <form onSubmit={handleSubmit}>
        <p>这旮瘩是表单</p>
        <button>提交</button>
      </form>
    </div>
  );

4.2 监听器中的this

在React16之前,事件的监听器中this可能会被频繁使用。因此我们需要记住this的绑定值。

但是在React18之后,即推出Hook之后,开发者就可以绕过 JS 中难以理解的this指向。

原生事件属性绑定的监听器中的this

<button onclick="onClick1()">原生事件1</button>
<button id="btn">原生事件2</button>
<script type="text/babel">
  function onClick1() {
    console.log(this); // undefined
  }
  
  function onClick2() {
    console.log(this); // button元素
  }
  document.getElementById('btn').onclick = onClick2;
</script>

但是在React中 监听器的this 与HTML属性注册事件是一样的,即监听器中的this不会绑定任何事。

<div id="root"></div>
<script type="text/babel">
function onClick() {
  console.log(this);// undefined 不会绑定任何值
}
let element = <button onClick={onClick}>click me</button>;
ReactDOM.createRoot(document.querySelector('#root')).render(element);
</script>

如果这些监听器函数中的this需要有一个有效值:

<div id="root"></div>
<script type="text/babel">
var obj = {name: 'big fat girl baby'};
function onClick() {
  console.log(this);// undefined 不会绑定任何值
}
let element = <button onClick={onClick.bind(obj)}>click me</button>;
ReactDOM.createRoot(document.querySelector('#root')).render(element);
</script>

4.3 给监听器传入额外参数

默认监听器中只会接收一个参数,即事件对象。

如果想要传入额外参数,像这样

function deleteListItem(e, id) {
  console.log(e, id);
}
let element = (
  <ul>
    <li>
      <div>
        <p>list - 1</p>
        {/* 使用箭头函数套壳 */}
        <button
          onClick={(e) => {
            deleteListItem(e, 1);
          }}
        >
          删除
        </button>
      </div>
    </li>
    <li>
      <div>
        <p>list - 2</p>
        {/* 也可以使用bind函数 */}
        <button onClick={deleteListItem.bind(null, 2)}>删除</button>
      </div>
    </li>
  </ul>
);

ReactDOM.createRoot(document.querySelector('#root')).render(element);
  • bind函数的实现 会导致传入参数位置固定,不够灵活
  • 自己套壳实现 可以自行传入参数,因此位置灵活
  • 实际应用 都可以。

5. 组件

React组件更像是一个函数,返回组件最终渲染后的效果(UI)。这里函数的名字就是组件名。为了和其他普通函数区分,组件的名称 遵循 大坨峰命名法(PascalCase)。

// 定义组件
function Greeting(){
  return <div className="greeting"><h3>大家,下午好。</h3></div>
}
// 组件的渲染
let element = <Greeting />; // 将组件 => jsx(React元素)
ReactDOM.createRoot(document.querySelector('#root')).render(element);

5.1 props

所有函数定义的组件 也都会接收一个唯一参数,即props。和Vue中一样,都是单向数据流。推荐数据是从父组件向其后代流动。

本质上 props 是一个 object对象。存储父组件给绑定的所有属性集合。

function Parent(props) {
  console.log('parent', props);
  return (
    <div
      className="parent"
      style={{ border: '1px solid red', padding: '10px' }}
    >
      <p>谁是爹》</p>
      <Child name="tom" age={18} />
    </div>
  );
}

function Child(props) {
  console.log('child', props);
  return (
    <div
      style={{ border: '1px solid green', padding: '10px' }}
      className="parent"
    >
      这是儿子,它叫 {props.name}。今年 {props.age} 岁。
    </div>
  );
}

let element = <Parent />; // 将组件 => jsx(React元素)
ReactDOM.createRoot(document.querySelector('#root')).render(element);

props 是 组件与外部数据交互的接口。同时还是响应式,即当props中数据发生变化后,组件会及时重新渲染。

==注意:props 是 "只读的"!,当在使用props的时候,虽然语法上是可以修改其属性,但是React会检查该操作,一旦发现就会抛出一个类型异常: Uncaught TypeError: Cannot assign to read only property 'age' of object。==

5.2 state

state 是 组件内部自己拥有的状态数据。不同于 props ,props 是 只读的,但是 state 是可以修改的。同时state变化后,组件会重新渲染。

如果想要组件内部拥有状态state,在不使用hooks时,必须通过es6中的类来定义组件,这样在类组件渲染时,会创建组件实例。通过组件实例的state属性 去存储 所有内部的状态(state类型为object对象)。

其中,类名就是组件名,也遵循PascalCase命名法。

// 定义类Greeting 继承自 React.Component
// 此时,即为Greeting 组件
class Greeting extends React.Component {
  // 定义render实例方法
  render() {
    return (
      <div>
        <h3>你好,兄弟!</h3>
      </div>
    );
  }
}

上述代码要求:

  1. 类必须继承React.Component才会是组件
  2. 必须含有render实例方法,用来返回组件最终渲染效果UI

接下来,看一个计数器案例:

class Counter extends React.Component {
// 添加状态
  state = {
    name: '计数器',
    count: 0, // 定义一个计数的状态值
  };
  reduceOne() {
    // console.log(this);
    // 类Vue,如果直接修改state可以吗?
    // this.state.count--;// wrong
    // 正确做法
  	this.setState({
    	count: this.state.count - 1,
  	});
  }
  addOne = () => {
    // console.log(this);
    this.setState({
      count: this.state.count + 1,
    });
	};

  render() {
    // 1 在类组件中 通过this先拿到组件实例,然后通过访问state属性得到所有的状态。
    return (
      <div>
        <h3>{this.state.name}</h3>
        <p>当前值:{this.state.count}</p>
        <p>
          <button onClick={this.reduceOne.bind(this)}>-</button>
          <button onClick={this.addOne}>+</button>
        </p>
      </div>
    );
  }
}

上面代码小结:

  1. 获取状态state:通过 this先拿到组件实例;再访问state属性获取到所有的状态。
  2. 修改状态state:必须通过组件实例的setState方法才可以。
  3. setState(newState | (prevState, props) => newState)该方法在修改状态时,会将旧的state与setState传入新的state合并后产生的新状态对象 替换 旧状态对象的方式来实现响应式的。因此在调用setState时,要么直接传入一个新的状态对象,或者传入一个函数,该回调函数返回一个新state对象。

5.3 组件生命周期

6. 条件渲染

7. 列表渲染

7.1 JS各种循环

7.2 数组map方法

8. Hooks

8.1 useState

8.2 useEffect

8.3 useMemo

8.4 useCallback

9. refs & dom