react笔记

2022/12/24 FrontEnd 共 13282 字,约 38 分钟

react笔记

React

React 介绍

React 是什么

  • Facebook 开源的一个JS库。
  • 一个用于动态构建用户界面的JS库。
  • 不是一个MCV框架,构建可复用web组件,侧重UI

React 的特点

  • Declarative(声明式编码)
  • Component-Based(组件化编码)
  • Learn Once, Write Anywhere(支持客户端、服务器端渲染)
  • 高效的DOM Diff算法,最小化页面重绘。虚拟(virtual)DOM,不总是直接操作DOM
  • 单向数据流 React是单向的从数据到视图的渲染,非双向数据绑定

React高效的原因

  • 虚拟(virtual)DOM,不总是直接操作DOM
  • 高效的DOM Diff算法,最小化页面重绘(即“局部渲染”)。

虚拟DOM指的是:在真实DOM的上一层映射一层虚拟DOM。我们操作的是映射关系,而不是真实的DOM。假设页面的样式做了修改(比如新增了一个标签),此时修改的是虚拟DOM的样式,真实的DOM并未发生变化。那什么时候,真实的DOM会发生变化呢? 当我把所有的内容操作完之后,转化为真实的DOM,此时要打包统一的渲染页面,于是真实的DOM发生变化,然后渲染一次。 这样做的话,可以减少页面的渲染次数。

JSX和组件

react 使用 JSX 来替代常规的 JavaScript。

JSX 是一个看起来很像 XML 的 JavaScript 语法扩展。

如果碰见jsx或者tsx文件,就是用了jsx语法。其中tsx是typescript+jsx

我们不需要一定使用 JSX,但它有以下优点:

  • JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化。
  • 它是类型安全的,在编译过程中就能发现错误。
  • 使用 JSX 编写模板更加简单快速。
const element = <h1>Hello, world!</h1>;

我们推荐在 React 中使用 JSX 来描述用户界面。

每个return jsx的叫组件。

对于原生的react代码,每次创建元素很麻烦,所以我们提出了用jsx语法代替,此时代码中return的<div></div>就是一种jsx语法。jsx语法需要注意的点在于:

  1. 对于jsx中的标签内如果需要传值,主要要使用{}包裹,如果使用”“,则默认会传一个string的值,容易造成错误
  2. 对于传入的对象,我们需要用大括号包裹(类似 {"key": "value"}),此时第一层是用来表示这里面是js代码,第二层用来表示这个是个对象
  3. jsx其实就是一种语法模板,他用来翻译react中书写的代码,而用来将jsx语法翻译的工具叫做babel-loader,而babel-loader被webpack内置了,因为我们用的脚手架其实就是webpack的打包,所以默认我们就可以写jsx语法而不报错。

###

JSX 允许在大括号中嵌入任何表达式,所以我们可以在 map() 中这样使用:

function NumberList(props) {
  const numbers = props.numbers;
  return (
    <ul>
      {numbers.map((number) =>
        <ListItem key={number.toString()}
                  value={number} />
 
      )}
    </ul>
  );
}

如果想在jsx里加注释,就{}包起来,

想加console.log,也是这样包起来。 {}其实是返回了一个值,这个值作为jsx里用的。

return (
  {console.log('xx')}
  {console.log('234')}
  )

每个组件就对应了一个html元素。html元素的属性会传递成组件的元素。

Hello world

<!DOCTYPE html>
<html lang="">
  <head>
    <meta />
    <meta />
    <meta />
    <title>Document</title>
  </head>
  <body>
    <!-- 引入React相关的js库 -->
    <script type="text/javascript" src="./libs/react.js"></script>
    <script type="text/javascript" src="./libs/react-dom.js"></script>
    <script type="text/javascript" src="./libs/babel.min.js"></script>

    <div id="myContainer"></div>

    <!-- 注意,这一行的 type 是写 "text/babel",而不是 "text/javascript" -->
    <script type="text/babel">

      //页面中的真实容器元素
      var containDiv = document.getElementById("myContainer");

      //1、创建虚拟DOM对象
      var vDom = <div>Hello, React!</div>; // 不是字符串, 不能加引号

      //2、渲染虚拟DOM对象(将虚拟DOM对象渲染到页面元素中)
      ReactDOM.render(vDom, containDiv); // 参数1:虚拟DOM对象;参数2:页面中的容器
    </script>
  </body>
</html>

React安装

<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<!-- 生产环境中不建议使用 -->
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>

自定义组件参数渲染

自定义组件两种方式,函数式组件和类组件。

以下代码实现了显示时间的时钟,react拿Date渲染。

function Clock(props) {
  return (
    <div>
      <h1>Hello, world!</h1>
      <h2>现在是 {props.date.toLocaleTimeString()}.</h2>
    </div>
  );
}
 
function tick() {
  ReactDOM.render(
    <Clock date={new Date()} />,
    document.getElementById('example')
  );
}
 
setInterval(tick, 1000);

除了函数外我们还可以创建一个 React.Component 的 ES6 类,该类封装了要展示的元素,需要注意的是在 render() 方法中,需要使用 this.props 替换 props:

class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>现在是 {this.props.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}
 
function tick() {
  ReactDOM.render(
    <Clock date={new Date()} />,
    document.getElementById('example')
  );
}
 
setInterval(tick, 1000);

复合组件

function Name(props) {
    return <h1>网站名称:{props.name}</h1>;
}
function Url(props) {
    return <h1>网站地址:{props.url}</h1>;
}
function Nickname(props) {
    return <h1>网站小名:{props.nickname}</h1>;
}
function App() {
    return (
    <div>
        <Name name="菜鸟教程" />
        <Url url="http://www.runoob.com" />
        <Nickname nickname="Runoob" />
    </div>
    );
}
 
ReactDOM.render(
     <App />,
    document.getElementById('example')
);

有状态的组件(定时刷新等)

React State(状态)

其实就是组件可以实现定时刷新等功能,不用操作DOM

定时功能

React 把组件看成是一个状态机(State Machines)。通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致。

React 里,只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。

注意必须要用react api来更新state,如果直接this.state赋值不会重绘。state其实就是一个Object可以多层结构 自定义。

每次调用this.setState就重绘一次。

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }
  // 挂载,注册定时任务
  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }
  // 卸载定时任务
  componentWillUnmount() {
    clearInterval(this.timerID);
  }
 
  tick() {
    this.setState({
      date: new Date()
    });
  }
 
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>现在是 {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}
 
ReactDOM.render(
  <Clock />,
  document.getElementById('example')
);

每当 Clock 组件第一次加载到 DOM 中的时候,我们都想生成定时器,这在 React 中被称为挂载。

同样,每当 Clock 生成的这个 DOM 被移除的时候,我们也会想要清除定时器,这在 React 中被称为卸载。

代码解析:

  • componentDidMount() 与 componentWillUnmount() 方法被称作生命周期钩子。
  • 在组件输出到 DOM 后会执行 componentDidMount() 钩子,我们就可以在这个钩子上设置一个定时器。
  • this.timerID 为定时器的 ID,我们可以在 componentWillUnmount() 钩子中卸载定时器。

数据自顶向下流动

父组件或子组件都不能知道某个组件是有状态还是无状态,并且它们不应该关心某组件是被定义为一个函数还是一个类。

这就是为什么状态通常被称为局部或封装。 除了拥有并设置它的组件外,其它组件不可访问。

State 和 Props 组件参数传递

State 和 Props 组件的参数传递、父组件传递给子组件

state 和 props 主要的区别在于 props 是不可变的,而 state 可以根据与用户交互来改变。这就是为什么有些容器组件需要定义 state 来更新和修改数据。 而子组件只能通过 props 来传递数据。

使用props

function HelloMessage(props) {
    return <h1>Hello {props.name}!</h1>;
}
 
const element = <HelloMessage name="Runoob"/>;
 
ReactDOM.render(
    element,
    document.getElementById('example')
);

设置默认props属性

class HelloMessage extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.name}</h1>
    );
  }
}
 
// default props
HelloMessage.defaultProps = {
  name: 'Runoob'
};
 
const element = <HelloMessage/>;
 
ReactDOM.render(
  element,
  document.getElementById('example')
props是只读的

所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。

组合使用 state 和 props

在应用中组合使用 state 和 props 。我们可以在父组件中设置 state, 并通过在子组件上使用 props 将其传递到子组件上。在 render 函数中, 我们设置 name 和 site 来获取父组件传递过来的数据。

class WebSite extends React.Component {
  constructor() {
      super();
 
      this.state = {
        name: "菜鸟教程",
        site: "https://www.runoob.com"
      }
    }
  render() {
    return (
      <div>
        <Name name={this.state.name} />
        <Link site={this.state.site} />
      </div>
    );
  }
}
 
 
 
class Name extends React.Component {
  render() {
    return (
      <h1>{this.props.name}</h1>
    );
  }
}

Props 验证

比如验证是String类型才行。

class MyTitle extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.title}</h1>
    );
  }
}
 
MyTitle.propTypes = {
  title: PropTypes.string
};

Props 验证使用 propTypes,它可以保证我们的应用组件被正确使用,React.PropTypes 提供很多验证器 (validator) 来验证传入数据是否有效。当向 props 传入无效数据时,JavaScript 控制台会抛出警告。

React 事件处理 按钮等逻辑

React 事件处理 按钮等逻辑

class Popper extends React.Component{
    constructor(){
        super();
        this.state = {name:'Hello world!'};
    }
    // onclick执行逻辑
    preventPop(name, e){    //事件对象e要放在最后
        e.preventDefault();
        alert(name);
    }
    
    render(){
        return (
            <div>
                <p>hello</p>
                {/* 通过 bind() 方法传递参数。 */}
                <a href="https://reactjs.org" onClick={this.preventPop.bind(this,this.state.name)}>Click</a>
            </div>
        );
    }
}

子组件如何给父组件传递消息? 参考

当你需要从子组件中更新父组件的 state 时,你需要在父组件通过创建事件句柄 (handleChange) ,并作为 prop (updateStateProp) 传递到你的子组件上。实例如下:

class Content extends React.Component {
  render() {
    return  <div>
              <button onClick = {this.props.updateStateProp}>点我</button>
              <h4>{this.props.myDataProp}</h4>
           </div>
  }
}
class HelloMessage extends React.Component {
  constructor(props) {
      super(props);
      this.state = {value: 'Hello Runoob!'};
      this.handleChange = this.handleChange.bind(this);
  }
  handleChange(event) {
    this.setState({value: '菜鸟教程'})
  }
  render() {
    var value = this.state.value;
    return <div>
            <Content myDataProp = {value} 
              updateStateProp = {this.handleChange}></Content>
           </div>;
  }
}
ReactDOM.render(
  <HelloMessage />,
  document.getElementById('example')
);

React 条件渲染

React 条件渲染

在jsx里定义条件或者变量来渲染可以不同的结果。

变量控制:

  render() {
    const isLoggedIn = this.state.isLoggedIn;
 
    let button = null;
    if (isLoggedIn) {
      button = <LogoutButton onClick={this.handleLogoutClick} />;
    } else {
      button = <LoginButton onClick={this.handleLoginClick} />;
    }
 
    return (
      <div>
        <Greeting isLoggedIn={isLoggedIn} />
        {button}
      </div>
    );

三目运算符

render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      {isLoggedIn ? (
        <LogoutButton onClick={this.handleLogoutClick} />
      ) : (
        <LoginButton onClick={this.handleLoginClick} />
      )}
    </div>
  );
}

React 列表 & Keys

React 列表 & Keys

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    <li key={number.toString()}> Keys 可以在 DOM 中的某些元素被增加或删除的时候帮助 React 识别哪些元素发生了变化。因此你应当给数组中的每一个元素赋予一个确定的标识。
      {number}
    </li>
  );
  return (
    <ul>{listItems}</ul>
  );
}
 
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('example')
);

Keys 可以在 DOM 中的某些元素被增加或删除的时候帮助 React 识别哪些元素发生了变化。因此你应当给数组中的每一个元素赋予一个确定的标识。

React 组件 API

React 组件 API

  • 设置状态:setState 合并nextState和当前state,并重新渲染组件。
    • setState是React事件处理函数中和请求回调函数中触发UI更新的主要方法。合并指如果json两个key,可以只操作一个而不影响另一个Key。
    • 另外注意setState()并不会立即改变this.state,而是创建一个即将处理的state。setState()并不一定是同步的,为了提升性能React会批量执行state和DOM渲染。
  • 替换状态:replaceState 不合并。
  • 设置属性:setProps 设置属性,合并。
  • 替换属性:replaceProps 替换属性
  • 强制更新:forceUpdate
    • forceUpdate()方法会使组件调用自身的render()方法重新渲染组件,组件的子组件也会调用自己的render()。但是,组件重新渲染时,依然会读取this.props和this.state,如果状态没有改变,那么React只会更新DOM。
    • forceUpdate()方法适用于this.props和this.state之外的组件重绘(如:修改了this.state后),通过该方法通知React需要调用render()
    • 一般来说,应该尽量避免使用forceUpdate(),而仅从this.props和this.state中读取状态并由React触发render()调用。
  • 获取DOM节点:findDOMNode
    • 返回值:DOM元素DOMElement
    • 如果组件已经挂载到DOM中,该方法返回对应的本地浏览器 DOM 元素。当render返回null 或 false时,this.findDOMNode()也会返回null。从DOM 中读取值的时候,该方法很有用,如:获取表单字段的值和做一些 DOM 操作。

React 组件生命周期

React 组件生命周期

  • Mounting(挂载):已插入真实 DOM
  • Updating(更新):正在被重新渲染
  • Unmounting(卸载):已移出真实 DOM

img

  • componentDidMount(): 在组件挂载后(插入 DOM 树中)立即调用。
  • componentDidUpdate(): 在重绘后会被立即调用。
  • componentWillUnmount(): 在组件卸载及销毁之前直接调用。

例子

  • React AJAX 使用ajax请求服务端数据,挂载时开始异步请求数据,并设置回调,如果请求有结果了,就setState重绘,卸载时取消进行中的请求。

React 表单

React 表单

HTML 表单元素与 React 中的其他 DOM 元素有所不同,因为表单元素生来就保留一些内部状态。

在 HTML 当中,像 <input>, <textarea>, 和 <select> 这类表单元素会维持自身状态,并根据用户输入进行更新。但在React中,可变的状态通常保存在组件的状态属性中,并且只能用 setState() 方法进行更新。

每个组件对应了一个html元素。html元素的属性会传递成组件的元素。

因此input value应该就是组件里state里的某个值,然后react 监听onchange,onchang时也同步更新state里value的值。相当于组件就变成了一个html表单元素。

例子:在实例中我们设置了输入框 input 值 value = {this.state.data}。在输入框值发生变化时我们可以更新 state。我们可以使用 onChange 事件来监听 input 的变化,并修改 state。

class HelloMessage extends React.Component {
  constructor(props) {
      super(props);
      this.state = {value: 'Hello Runoob!'};
      this.handleChange = this.handleChange.bind(this);
  }
 
  handleChange(event) {
    this.setState({value: event.target.value});
  }
  render() {
    var value = this.state.value;
    return <div>
            <input type="text" value={value} onChange={this.handleChange} /> 
            <h4>{value}</h4>
           </div>;
  }
}
ReactDOM.render(
  <HelloMessage />,
  document.getElementById('example')
);

子组件想更新父组件的值,就让父组件传回到函数给子组件。

父组件想更新子组件的值,就属性传递即可。父组件每次重绘,都会重绘子组件。

函数组件代替类组件

函数组件代替类组件

函数组件可以代替类组件,因为函数组件语法更加简单易懂,但是也面临以下两个问题

  • 没有state

    • React v16.8.0推出Hooks API,其中的 useState 可以解决这个问题

      注意: useState 不可以用 if...else

  • 没有生命周期

    • React v16.8.0推出Hooks API,其中的 useEffect 可以解决这个问题

      默认每次渲染都会调用

Hook 是一个特殊的函数,它可以让你钩入React 的特性。例如,useState 是允许你在 React 函数组件中添加 state 的 Hook。

函数组件和类组件对比

  • 一般来说,类组件的性能消耗较函数式组件大,包括生成实例时使用的内存及shouldComponentUpdate不正确引起的不必要的更新。
  • 类组件不能很好的压缩
  • 实际开发中,类组件的生命周期常常包含一些不相关的逻辑

状态 useState

useState

state可以起别名,此时多次调用useState其实就设置了不同别名不同值。

import React, { useState, useEffect } from "react"

const App = props =>{//消除了this
    const [n, setN] = React.useState(0)//数组前面是读,后面是写,叫法无所谓
    const onClick = () => {
        setN(n+1)
    }
    return {
        <div>
        {n},
        <button onClick = {onClick}>+1</button>
        </div>
    }
}

以上方法比原先的setState要简便不少,但是麻烦的是如果有多个数据,需要多次useState而不能一次性传入多个值。

生命周期 useEffect

useEffect

effect(副作用)是react函数组件用来替代生命周期的函数。你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。

useEffect 接受一个函数作为参数,第二个参数表示什么时候调用它。 参数如下: useEffect(callback[,[]])

第一个参数接受一个callback回调函数,里面可以写执行业务代码

第二个参数可省略,它接受一个数组,可以是空数组,也包含了state数据

可以模拟componentDidMount componentDidUpdate componentWillUnmount

  • 当不传递第二个参数时,每次render都会执行一遍callback函数,相当于包含第一遍render的componentDidUpdate

  • 当传递第二个参数且是空数组时,只有第一次render才会执行callback,类似于componentDidMount

  • 不管是否传递第二个参数,只要在callback中return 一个函数,就相当于告诉react此组件挂掉之前执行什么操作,类似于componentWillUnMount

  • 可以监听指定是数据的变化,有变化就调用指定的函数。此时第二个参数是数组且不为空,比如

    React.useEffect(() => {
        console.log("state发生变化了");
      }, [state]); //这里传递了数组,里面是监控的数据,也可以多个变量
    

useContext

参考

context(上下文)可以看成是扩大版的props,它可以将全局的数据通过provider接口传递value给局部的组件,让包围在provider中的局部组件可以获取到全局数据的读写接口

全局变量可以看成是全局的上下文

而上下文则是局部的全局变量,因为只有包围在provider中的局部组件才可以获取到这些全局变量的读写接口

设置一个父组件,一个子组件,通过useContext来传递state,并在子组件上设置一个按钮来改变全局state

这样子组件可以读父组件的state,他们其实就是state共享了。

父组件也可以读子组件设置的。

const { state, setState } = useContext(X);//拿到 名字为X的上下文的value,用两个变量来接收读写接口

useReducer

什么是useReducer

Redux 的核心概念是,组件发出 action 与状态管理器通信。状态管理器收到 action 以后,使用 Reducer 函数算出新的state,Reducer 函数的形式是(state, action) => newState

useReducer用来引入 Reducer 功能。

useReducer语法

const [state, dispatch] = useReducer(reducer, initialState);

useReducer用来接收两个参数,分别是Reducer函数和初始state

useReducer 返回了一个数组,2个元素分别为 state 和 dispatch 方法。其中 state 在我们的例子中就是当前的 n 值,dispatch 方法接受一个参数,执行对应的 action。dispatch 执行后,对应的 state 会改变,组件会 rerender,来展示最新的状态。

reducer函数里面可以存放state的各种操作,它类似状态管理器(其实我觉得应该叫state数据管理器),通过dispatch函数的传递action,可以触发reducer函数内部运算操作,并返回当前state

一个dispatch例子

定义加法和减法
const reducer=(state,action)=>{
	if(action.type==="add"){
    	return {
        	n:state.n+1
        }
    }else if(action.type==="sub"){
    	return {
        	n:state.n-1
        }
    }
}

其次,使用useReducer接收Reducer函数和一个初始state,并返回当前值state与dispatch函数

当触发事件时,使用dispatch传递action,让reducer计算出新的state

export default function App() {
  const initialState = { n: 1 };
  const [state, dispatch] = useReducer(reducer, initialState); 
//使用useReducer接收reducer参数和初始state
  return (
    <>
      <div>{state.n}</div>
      <button
        onClick={() => {
          dispatch({ type: "add" });  // 传递action
        }}
      >
        点击+
      </button>
      <button
        onClick={() => {
          dispatch({ type: "sub" });//传递action触发reducer函数
        }}
      >
        点击-
      </button>
    </>
  );
}

dispatch()是发出 Action 的唯一方法。它发出action后使reducer执行,并返回一个新的state。

配合useContext

我们可以使用useContext配合useReducer来帮助我们完成一些数据、操作的流转

参考

Search

    Table of Contents