1 Star 1 Fork 0

ANBK/React-Study

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README

React 入门

英文官网传送门

中文官网传送门

React 简介

React 为何物

React:用于构建用户界面的 JavaScript 库。由Facebook 开发且开源。

为何学习 React

原生 JavaScript 的痛点:

  • 操作 DOM 繁琐、效率低
  • 使用 JavaScript 直接操作 DOM,浏览器进行大量重绘重排
  • 原生 JavaScript 没有组件化编码方案,代码复用率低

React 的特点:

  • 采用组件化模式、声明式编码,提高开发效率和组件复用率
  • React Native 中可用 React 语法进行移动端开发
  • 使用虚拟 DOM 和 Diffing 算法,减少与真实 DOM 的交互

React 初体验

来一发 Hello React

相关 JS 库:

  • react.development.js :React 核心库
  • react-dom.development.js :提供 DOM 操作的 React 扩展库
  • babel.min.js :解析 JSX 语法,转换为 JS 代码
<!-- 准备好一个“容器” -->
<div id="test"></div>

<!-- 引入react 核心库 -->
<script
  type="text/javascript"
  src="../../react-js/react.development.js"
></script>
<!-- 引入react-dom 用于支持react操作dom -->
<script
  type="text/javascript"
  src="../../react-js/react-dom.development.js"
></script>
<!-- 引入babel 用于jsx转为js -->
<script type="text/javascript" src="../../react-js/babel.min.js"></script>

<!-- 此处一定要写babel,表示写的不是 JS,而是 JSX,并且靠 babel 翻译 -->
<script type="text/babel">
  //1.创建虚拟DOM
  // 不要写引号,因为不是字符串
  const VDOM = <h1>Hello,React</h1>;

  //2.渲染虚拟DOM到页面
  // 导入核心库和扩展库后,会有 React 和 ReactDOM 两个对象
  ReactDOM.render(VDOM, document.getElementById('test'));
</script>

创建虚拟 DOM 的两种方式:JS 和 JSX

  • 使用 JS 创建虚拟 DOM 比 JSX 繁琐
  • JSX 可以让程序员更加简单地创建虚拟 DOM,相当于语法糖
  • 最终 babel 会把 JSX 语法转换为 JS
<script type="text/javascript">
  //1.使用 React 提供的 API 创建虚拟DOM
  const VDOM = React.createElement(
    'h1',
    { id: 'title' },
    React.createElement('span', {}, 'Hello,React')
  );
  //2.渲染虚拟DOM到页面
  ReactDOM.render(VDOM, document.getElementById('test'));
</script>
<script type="text/babel">
  //1.创建虚拟DOM
  const VDOM = (
    <h1 id='title'>
      <span>Hello,React</span>
    </h1>
  );
  //2.渲染虚拟DOM到页面
  ReactDOM.render(VDOM, document.getElementById('test'));
</script>

虚拟 DOM && 真实 DOM

关于虚拟 DOM:

  1. 本质是 Object 类型的对象(一般对象)
  2. 虚拟 DOM 比较“轻”,真实 DOM 比较“重”,因为虚拟 DOM 是 React 内部在用,无需真实 DOM 上那么多的属性。
  3. 虚拟 DOM 最终会被 React 转化为真实 DOM,呈现在页面上。
<script type="text/babel">
  const VDOM = (
    <h1 id='title'>
      <span>Hello,React</span>
    </h1>
  );
  ReactDOM.render(VDOM, document.getElementById('test'));

  const TDOM = document.getElementById('demo');
  console.log('虚拟DOM', VDOM);
  console.log('真实DOM', TDOM);
</script>

JSX

JSX 简介

  • 全称:JavaScript XML
  • React 定义的类似于 XML 的 JS 扩展语法;本质是 React.createElement() 方法的语法糖
  • 作用:简化创建虚拟 DOM

JSX 语法规则

  • 定义虚拟 DOM 时,不要写引号
  • 标签中混入 JS 表达式需要使用 {}
  • 指定类名不用 class,使用 className
  • 内联样式,使用 style={ { key: value } } 的形式
  • 只能有一个根标签
  • 标签必须闭合,单标签结尾必须添加 /:<input type="text" />
  • 标签首字母小写,则把标签转换为 HTML 对应的标签,若没有,则报错
  • 标签首字母大写,则渲染对应组件,若没有定义组件,则报错
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>jsx语法规则</title>
    <style>
      .title {
        background-color: orange;
        width: 200px;
      }
    </style>
  </head>
  <body>
    <div id="test"></div>
    ...
    <script type="text/babel">
      const myId = 'aTgUiGu';
      const myData = 'HeLlo,rEaCt';

      const VDOM = (
        <div>
          <h2 className='title' id={myId.toLowerCase()}>
            <span style={{ color: 'white', fontSize: '19px' }}>
              {myData.toLowerCase()}
            </span>
          </h2>
          <input type='text' />
          // <good>very good</good>
          // <Child></Child>
        </div>
      );

      ReactDOM.render(VDOM, document.getElementById('test'));
    </script>
  </body>
</html>

JSX 例子

注意区分:JS 语句(代码) 与 JS 表达式

  1. 表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
a;
a + b;
demo(1);
arr.map();
function test() {}
  1. 语句(代码):
if(){}
for(){}
switch(){case:xxxx}
<script type="text/babel">
  let list = ['Angular', 'React', 'Vue'] const VDOM = (
  <div>
    <h1>前端js框架列表</h1>
    <ul>
      // React 会自动遍历数组
      {list.map((item, index) => {
        // Each child in a list should have a unique "key" prop.
        return <li key={index}>{item}</li>
      })}
    </ul>
  </div>
  ) ReactDOM.render(VDOM, document.getElementById('test'))
</script>

React 面向组件编程

函数式组件

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

      /*
        执行了ReactDOM.render(<MyComponent />....)之后,发生了什么?
          1.React解析组件标签,找到了MyComponent组件
          2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中
      */
    </script>

要点:

  • 组件名称首字母必须大写,否则会解析成普通标签导致报错,详见 JSX 语法规则
  • 函数需返回一个虚拟 DOM
  • 渲染组件时需要使用标签形式,同时标签必须闭合

渲染组件的过程:

  • React 解析标签,寻找对应组件
  • 发现组件是函数式组件,则调用函数,将返回的虚拟 DOM 转换为真实 DOM ,并渲染到页面中

类式组件

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

      /*
        执行了ReactDOM.render(<MyComponent />....)之后,发生了什么?
          1.React解析组件标签,找到了MyComponent组件。
          2.发现组件是使用类定义的,随后new出该类的实例,并通过该实例调用到原型上的render方法。
          3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。
      */
    </script>

组件渲染过程:

  • React 解析组件标签,寻找组件
  • 发现是类式组件,则 new 该类的实例对象,通过实例调用原型上的 render 方法
  • 将 render 返回的虚拟 DOM 转为真实 DOM ,渲染到页面上

组件实例核心属性 state

state 是组件实例对象最重要的属性,值为对象。又称为状态机,通过更新组件的 state 来更新对应的页面显示。

要点:

  • 初始化 state
  • React 中事件绑定
  • this 指向问题
  • setState 修改 state 状态
  • constructor 、render 、自定义方法的调用次数
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>state</title>
  </head>
  <body>
    <!-- 准备好一个容器 -->
    <div id="test"></div>

    <!-- 引入react 核心库 -->
    <script
      type="text/javascript"
      src="../../react-js/react.development.js"
    ></script>
    <!-- 引入react-dom 用于支持react操作dom -->
    <script
      type="text/javascript"
      src="../../react-js/react-dom.development.js"
    ></script>
    <!-- 引入babel 用于jsx转为js -->
    <script type="text/javascript" src="../../react-js/babel.min.js"></script>

    <script type="text/babel">
      // 1.创建类组件
      class Weather extends React.Component {
        // constructor调用几次? ———— 1次
        constructor(props) {
          console.log('constructor');
          super(props);
          // 初始化状态
          this.state = { isHot: true, wind: '微风' };
          //解决changeWeather中的this指向问题
          this.changeWeather = this.changeWeather.bind(this);
        }

        // render调用几次? ———— 1+n次 1是初始化的那次,n是状态更新的次数
        render() {
          console.log('render');
          const { isHot, wind } = this.state;
          return (
            <h1 onClick={this.changeWeather}>
              今天天气很{isHot ? '炎热' : '凉爽'},{wind}
            </h1>
          );
        }

        // changeWeather调用几次? ———— 点几次调几次
        changeWeather() {
          console.log('changeWeather');
          // changeWeather方法放在了哪里 —— Weather的原型对象上,供实例使用
          // 由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
          // 类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined

          // 获取原来的值
          const isHot = this.state.isHot;
          // 严重注意:状态必须通过setState进行更新,且更新是一种合并,不是替换
          this.setState({ isHot: !isHot });

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

简化版:

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

    render() {
      const { isHot } = this.state;
      return (
        <h2 onClick={this.changeWeather}>天气{isHot ? '炎热' : '凉爽'}</h2>
      );
    }

    // 采用箭头函数 + 赋值语句形式
    changeWeather = () => {
      const isHot = this.state.isHot;
      this.setState = { isHot: !isHot };
    };
  }

  ReactDOM.render(<Weather />, document.getElementById('test'));
</script>

总结:

  1. 组件的 render 方法中的 this 为组件实例对象
  2. 组件自定义的方法中的 this 为 undefined,如何解决?
1. 强制绑定this:通过函数对象的bind()方法
2. 箭头函数
  1. 状态数据,不能直接修改或更新

组件实例核心属性 props

每个组件对象都有props 属性,组件标签的属性都保存在props 中。 props是只读的,不能修改。

props 基本使用

<script type="text/babel">
  class Person extends React.Component {
    render() {
      const { name, age, sex } = this.props;
      return (
        <ul>
          <li>姓名:{name}</li>
          <li>性别:{sex}</li>
          <li>年龄:{age}</li>
        </ul>
      );
    }
  }

  // 类似于标签属性传值
  ReactDOM.render(
    <Person name='Lily' age={19} sex='男' />,
    document.getElementById('test')
  );
</script>

批量传递 props

<script type="text/babel">
  class Person extends React.Component {
    render() {
      const { name, age, sex } = this.props;
      return (
        <ul>
          <li>姓名:{name}</li>
          <li>性别:{sex}</li>
          <li>年龄:{age}</li>
        </ul>
      );
    }
  }

  const obj = { name: 'Ben', age: 21, sex: '女' };
  ReactDOM.render(<Person {...obj} />, document.getElementById('test'));
</script>

限制 props

React 15.5 以前,React身上有一个PropTypes 属性可直接使用,即 name: React.PropTypes.string.isRequired ,没有把PropTypes 单独封装为一个模块。

React 15.5 开始,把PropTypes 单独封装为一个模块,需要额外导入使用。

<!-- 引入prop-types,用于对组件标签属性进行限制 -->
<script type="text/javascript" src="../../react-js/prop-types.js"></script>

<script type="text/babel">
  // 1.创建组件
  class Person extends React.Component {
    render() {
      const { name, age, sex } = this.props;
      return (
        <ul>
          <li>姓名:{name}</li>
          <li>年龄:{age + 1}</li>
          <li>性别:{sex}</li>
        </ul>
      );
    }
  }
  // 对标签属性进行类型,必要性的限制
  Person.propTypes = {
    name: PropTypes.string.isRequired, //限制name必传,且为字符串
    age: PropTypes.number, //限制age为数值
    sex: PropTypes.string, //限制sex为字符串
    speak: PropTypes.func, //限制speak为函数
  };
  // 指定默认标签属性值
  Person.defaultProps = {
    sex: '男', //sex默认值为男
    age: 18, //age默认值为18
  };
  // 2.渲染组件到页面
  ReactDOM.render(
    <Person name='jerry' speak={speak} />,
    document.getElementById('test')
  );

  function speak() {
    console.log('我说话了');
  }
</script>

props 的简写方式

Person.propTypesPerson.defaultProps 可以看作在类身上添加属性,利用static 关键词就能在类内部进行声明。因此所谓简写只是从类外部移到类内部。

<!-- 引入prop-types,用于对组件标签属性进行限制 -->
<script type="text/javascript" src="../../react-js/prop-types.js"></script>

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

  function speak() {
    console.log('我说话了');
  }
</script>

类组件的构造器与 props

官方文档说明

构造函数一般用在两种情况:

  • 通过给 this.state 赋值对象来初始化内部 state
  • 为事件处理函数绑定实例
constructor(props) {
  super(props)
  // 初始化 state
  this.state = { isHot: true, wind: '微风' }
  // 解决 this 指向问题
  this.changeWeather = this.changeWeather.bind(this)
}

因此构造器一般都不需要写。如果要在构造器内使用 this.props 才声明构造器,并且需要在最开始调用 super(props) :

constructor(props) {
  super(props)
  console.log(this.props)
}

函数式组件使用 props

由于函数可以传递参数,因此函数式组件可以使用props

<!-- 引入prop-types,用于对组件标签属性进行限制 -->
<script type="text/javascript" src="../js/prop-types.js"></script>

<script type="text/babel">
  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,
    sex: PropTypes.string,
    age: PropTypes.number,
  };

  Person.defaultProps = {
    sex: '男',
    age: 18,
  };

  ReactDOM.render(<Person name='jerry' />, document.getElementById('test'));
</script>

组件实例核心属性 refs

通过定义 ref 属性可以给标签添加标识。

字符串形式的 ref

这种形式已过时,效率不高,官方不建议使用。

<script type="text/babel">
  // 创建组件
  class RefDom extends React.Component {
    // 展示左侧输入框数据
    showData = () => {
      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(<RefDom />, document.getElementById('test'));
</script>

回调形式的 ref

要点:

  • c => this.input1 = c 就是给组件实例添加 input1 属性,值为节点
  • 由于是箭头函数,因此 this 是 render 函数里的 this ,即组件实例
<script type="text/babel">
  // 创建组件
  class RefDom extends React.Component {
    // 展示左侧输入框数据
    showData = () => {
      console.log(this);
      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
            ref={(c) => (this.input2 = c)}
            onBlur={this.showData2}
            type='text'
            placeholder='失去焦点提示数据'
          />
        </div>
      );
    }
  }
  // 渲染组件到页面
  ReactDOM.render(<RefDom />, document.getElementById('test'));
</script>

关于回调 ref 执行次数的问题,官网描述:

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

即内联函数形式,在更新过程中 ref 回调会被执行两次,第一次传入 null ,第二次传入 DOM 元素。若是下述形式,则只执行一次。但是对功能实现没有影响,因此一般也是用内联函数形式。

<script type="text/babel">
  //创建组件
  class Demo extends React.Component {
    state = { isHot: false };

    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={this.saveInput} type='text' />
        </div>
      );
    }
  }

  ReactDOM.render(<Demo />, document.getElementById('test'));
</script>

createRef API

该方式通过调用 React.createRef 返回一个容器用于存储节点,且一个容器只能存储一个节点。

<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 />, document.getElementById('test'));
</script>

事件处理

  • React 使用自定义事件,而非原生 DOM 事件,即 onClick、onBlur :为了更好的兼容性
  • React 的事件通过事件委托方式进行处理:为了高效
  • 通过 event.target 可获取触发事件的 DOM 元素:勿过度使用 ref

当触发事件的元素和需要操作的元素为同一个时,可以不使用 ref :

class Demo extends React.Component {
  showData2 = (event) => {
    alert(event.target.value);
  };

  render() {
    return (
      <div>
        <input
          onBlur={this.showData2}
          type='text'
          placeholder='失去焦点提示数据'
        />
        &nbsp;
      </div>
    );
  }
}

受控 & 非受控组件

包含表单的组件分类:

  • 非受控组件:现用现取。即需要使用时,再获取节点得到数据
  • 受控组件:类似于 Vue 双向绑定的从视图层绑定到数据层

尽量使用受控组件,因为非受控组件需要使用大量的ref

// 非受控组件
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>
    );
  }
}
// 受控组件
class Login extends React.Component {
  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' />
        密码:
        <input onChange={this.savePassword} type='password' name='password' />
        <button>登录</button>
      </form>
    );
  }
}

对上述受控组件的代码进行优化,希望把 saveUsername 和 savePassword 合并为一个函数。

要点:

  • 高阶函数:参数为函数或者返回一个函数的函数,如 Promise、setTimeout、Array.map()
  • 函数柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式
// 函数柯里化
function sum(a) {
  return (b) => {
    return (c) => {
      return a + b + c;
    };
  };
}
// 使用高阶函数和柯里化写法
class Login extends React.Component {
  state = {
    username: '',
    password: '',
  };

  saveFormData = (dataType) => {
    return (event) => {
      this.setState({ [dataType]: event.target.value });
    };
  };

  handleSubmit = (event) => {
    event.preventDefault();
    const { username, password } = this.state;
    alert(`用户名是:${username}, 密码是:${password}`);
  };
  render() {
    return (
      <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>
    );
  }
}
// 不使用柯里化写法
class Login extends React.Component {
  state = {
    username: '',
    password: '',
  };

  saveFormData = (dataType, event) => {
    this.setState({ [dataType]: event.target.value });
  };

  handleSubmit = (event) => {
    event.preventDefault();
    const { username, password } = this.state;
    alert(`用户名是:${username}, 密码是:${password}`);
  };
  render() {
    return (
      <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>
    );
  }
}

不使用函数柯里化实现

<script type="text/babel">
  // 创建组件
  class Login extends React.Component {
    // 初始化状态
    state = {
      username: '',
      password: '',
    };
    // 保存表单数据到状态中【高阶函数&&函数柯里化】
    saveFormData = (typeData, event) => {
      this.setState({ [typeData]: event.target.value });
    };
    // 表单提交的回调
    handleSubmit = (event) => {
      event.preventDefault();
      const { username, password } = this.state;
      alert(`你输入的用户名是${username},你输入的密码是${password}`);
    };
    render() {
      return (
        <form action='#' onSubmit={this.handleSubmit}>
          用户名:
          <input
            onChange={(event) => {
              this.saveFormData('username', event);
            }}
            type='text'
            name='username'
          />
          密码:
          <input
            onChange={(event) => {
              this.saveFormData('password', event);
            }}
            type='text'
            name='password'
          />
          <button>登录</button>
        </form>
      );
    }
  }
  // 渲染组件
  ReactDOM.render(<Login />, document.getElementById('test'));
</script>

高阶函数 & 函数柯里化

要点: 高阶函数

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

                1. 若A函数,接收的参数是一个函数式,那么A函数就可以称之为高阶函数。
                2. 若A函数,调用的返回值依然是一个函数,那么A函数就可以称之为高阶函数。
                常见的高阶函数:Promise,setTimeout,arr.map()等等

函数柯里化

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

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>高阶函数&&函数柯里化</title>
  </head>
  <body>
    <!-- 准备好一个容器 -->
    <div id="test"></div>

    <!-- 引入react 核心库 -->
    <script
      type="text/javascript"
      src="../../react-js/react.development.js"
    ></script>
    <!-- 引入react-dom 用于支持react操作dom -->
    <script
      type="text/javascript"
      src="../../react-js/react-dom.development.js"
    ></script>
    <!-- 引入babel 用于jsx转为js -->
    <script type="text/javascript" src="../../react-js/babel.min.js"></script>

    <script type="text/babel">
      /*
          高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数
                    1. 若A函数,接收的参数是一个函数式,那么A函数就可以称之为高阶函数。
                    2. 若A函数,调用的返回值依然是一个函数,那么A函数就可以称之为高阶函数。
                    常见的高阶函数:Promise,setTimeout,arr.map()等等

            函数柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
                    function sum1(a) {
                        return (b) => {
                            return (c) => {
                                return a + b + c;
                            };
                        };
                     }
                        
      */
      // 创建组件
      class Login extends React.Component {
        // 初始化状态
        state = {
          username: '',
          password: '',
        };
        // 保存表单数据到状态中【高阶函数&&函数柯里化】
        saveFormData = (typeData) => {
          console.log(typeData);
          return (event) => {
            this.setState({ [typeData]: event.target.value });
          };
        };
        // 表单提交的回调
        handleSubmit = (event) => {
          event.preventDefault();
          const { username, password } = this.state;
          alert(`你输入的用户名是${username},你输入的密码是${password}`);
        };
        render() {
          return (
            <form action='#' onSubmit={this.handleSubmit}>
              用户名:
              <input
                onChange={this.saveFormData('username')}
                type='text'
                name='username'
              />
              密码:
              <input
                onChange={this.saveFormData('password')}
                type='text'
                name='password'
              />
              <button>登录</button>
            </form>
          );
        }
      }
      // 渲染组件
      ReactDOM.render(<Login />, document.getElementById('test'));
    </script>
  </body>
</html>

生命周期

引出生命周期

 <script type="text/babel">
      // 创建组件
      //  生命周期回调函数 <=> 生命周期钩子函数 <=> 生命周期函数 <=> 生命周期钩子
      class Life extends React.Component {
        state = { opacity: 1 };

        giveUp = () => {
          // 卸载组件
          ReactDOM.unmountComponentAtNode(document.getElementById('test'));
        };

        // 组件挂载完毕
        componentDidMount() {
          this.timer = setInterval(() => {
            // 获取原来的状态
            let { opacity } = this.state;
            // 减小0.1
            opacity -= 0.1;
            if (opacity <= 0) opacity = 1;
            // 更新状态
            this.setState({ opacity });
          }, 200);
        }

        // 组件将要卸载
        componentWillUnmount() {
          // 清除定时器
          clearInterval(this.timer);
        }
        // 初始化渲染,状态更新之后
        render() {
          console.log('@');
          return (
            <div>
              <h2 style={{ opacity: this.state.opacity }}>React学不会呀!</h2>
              <button onClick={this.giveUp}>放弃吧</button>
            </div>
          );
        }
      }
      // 渲染组件
      ReactDOM.render(<Life />, document.getElementById('test'));
    </script>

生命周期旧版

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

  • constructor
  • componentWillMount
  • render
  • componentDidMount

更新阶段

  1. 父组件重新render 触发的更新
  • componentWillReceiveProps
  • shouldComponentUpdate :控制组件是否更新的阀门,返回值为布尔值,默认为true 。若返回false ,则后续流程不会进行。
  • componentWillUpdate
  • render
  • componentDidUpdate
  1. 组件内部调用 this.setState() 修改状态
  • shouldComponentUpdate
  • componentWillUpdate
  • render
  • componentDidUpdate
  1. 组件内部调用 this.forceUpdate() 强制更新
  • componentWillUpdate
  • render
  • componentDidUpdate

卸载阶段:ReactDOM.unmountComponentAtNode() 触发

  • componentWillUnmount

案例:

     <script type="text/babel">
      // 创建组件
      class Count extends React.Component {
        // 构造器
        constructor(props) {
          console.log('Count---constructor');
          super(props);
          // 初始化状态
          this.state = { count: 0 };
        }

        // 加1按钮的回调
        add = () => {
          // 获取原状态
          const { count } = this.state;
          //  更新状态
          this.setState({ count: count + 1 });
        };

        // 卸载组件按钮的回调
        death = () => {
          ReactDOM.unmountComponentAtNode(document.getElementById('test'));
        };

        // 强制更新按钮的回调
        force = () => {
          this.forceUpdate();
        };

        // 组件将要挂载的钩子
        componentWillMount() {
          console.log('Count---componentWillMount');
        }

        // 组件挂载完毕的钩子
        componentDidMount() {
          console.log('Count---componentDidMount');
        }

        // 组件将要卸载的钩子
        componentWillUnmount() {
          console.log('Count---componentWillUnmount');
        }

        // 控制组件更新的阀门
        shouldComponentUpdate() {
          console.log('Count---shouldComponentUpdate');
          return true;
        }

        // 组件将要更新的钩子
        componentWillUpdate() {
          console.log('Count---componentWillUpdate');
        }
        // 组件更新完毕的钩子
        componentDidUpdate() {
          console.log('Count---componentDidUpdate');
        }

        // 初始化,更新状态时
        render() {
          console.log('Count---render');
          const { count } = this.state;
          return (
            <div>
              <h2>当前求和的值为{count}</h2>
              <button onClick={this.add}>点我+1</button>
              <button onClick={this.death}>卸载组件</button>
              <button onClick={this.force}>
                不更新状态中的数据,强制更新一下
              </button>
            </div>
          );
        }
      }

      class A extends React.Component {
        // 初始化状态
        state = { carName: '奔驰' };

        changeCarname = () => {
          this.setState({ carName: '奥迪' });
        };
        render() {
          return (
            <div>
              <div>我是A组件</div>
              <button onClick={this.changeCarname}>换车</button>
              <B carName={this.state.carName} />
            </div>
          );
        }
      }
      class B extends React.Component {
        // 组件将要接收新的props的钩子
        componentWillReceiveProps() {
          console.log('B---componentWillReceiveProps');
        }

        //控制组件更新的阀门
        shouldComponentUpdate() {
          console.log('B---shouldComponentUpdate');
          return true;
        }
        // 组件将要更新的钩子
        componentWillUpdate() {
          console.log('B---componentWillUpdate');
        }
        //组件更新完毕的钩子
        componentDidUpdate() {
          console.log('B---componentDidUpdate');
        }
        render() {
          console.log('B---render');
          return (
            <div>
              我是B组件,喜欢的车是
              <span style={{ color: 'orangered' }}>{this.props.carName}</span>
            </div>
          );
        }
      }
      // 渲染组件
      ReactDOM.render(<A />, document.getElementById('test'));
    </script>

生命周期新版

更新内容

  • 废弃三个钩子:componentWillMountcomponentWillReceivePropscomponentWillUpdate 。在新版本中这三个钩子需要加UNSAFE_ 前缀才能使用,后续可能会废弃。
  • 新增两个钩子(实际场景用得很少):getDerivedStateFromPropsgetSnapshotBeforeUpdate

` static getDerivedStateFromProps(props, state):

  • 需使用 static 修饰
  • 需返回一个对象更新 state 或返回 null
  • 适用于如下情况:state 的值任何时候都取决于 props

getSnapshotBeforeUpdate(prevProps, prevState):

  • 在组件更新之前获取快照
  • 得组件能在发生更改之前从 DOM 中捕获一些信息(如滚动位置)
  • 返回值将作为参数传递给 componentDidUpdate()
// static getDerivedStateFromProps 案例
static getDerivedStateFromProps(props,state){
  console.log('getDerivedStateFromProps',props,state);
  return null
}

getSnapshotBeforeUpdate(){
  console.log('getSnapshotBeforeUpdate');
  return 'atguigu'
}

componentDidUpdate(preProps,preState,snapshotValue){
  console.log('componentDidUpdate',preProps,preState,snapshotValue);
}
// getSnapshotBeforeUpdate 案例
class NewsList extends React.Component {
  state = { newsArr: [] };

  componentDidMount() {
    setInterval(() => {
      //获取原状态
      const { newsArr } = this.state;
      //模拟一条新闻
      const news = '新闻' + (newsArr.length + 1);
      //更新状态
      this.setState({ newsArr: [news, ...newsArr] });
    }, 1000);
  }

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

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

  render() {
    return (
      <div className='list' ref='list'>
        {this.state.newsArr.map((n, index) => {
          return (
            <div key={index} className='news'>
              {n}
            </div>
          );
        })}
      </div>
    );
  }
}
ReactDOM.render(<NewsList />, document.getElementById('test'));

最重要的三个钩子

  • render :初始化渲染和更新渲染
  • componentDidMount :进行初始化,如开启定时器、发送网络请求、订阅消息
  • componentWillUnmount :进行收尾,如关闭定时器、取消订阅消息

虚拟 DOM 和 Diff 算法

key的作用:

key是虚拟 DOM 对象的标识,可提高页面更新渲染的效率。 当状态中的数据发生变化时,React 会根据新数据生成新的虚拟 DOM ,接着对新旧虚拟 DOM 进行 Diff 比较,规则如下:

  • 旧虚拟 DOM 找到和新虚拟 DOM 相同的 key:
    • 若内容没变,直接复用真实 DOM
    • 若内容改变,则生成新的真实 DOM ,替换页面中之前的真实 DOM
  • 旧虚拟 DOM 未找到和新虚拟 DOM 相同的 key:根据数据创建新的真实 DOM ,渲染到页面

使用 index 作为 key 可能引发的问题:

  • 若对数据进行逆序添加、逆序删除等破坏顺序的操作,会进行没有必要的真实 DOM 更新。界面效果没问题,但效率低下。
  • 如果结构中包含输入类的 DOM(如 input 输入框) ,则会产生错误的 DOM 更新。
  • 若不存在对数据逆序添加、逆序删除等破坏顺序的操作,则没有问题。
// 使用 index 作为 key 引发的问题
class Person extends React.Component {
  state = {
    persons: [
      { id: 1, name: '小张', age: 18 },
      { id: 2, name: '小李', age: 19 },
    ],
  }

  add = () => {
    const { persons } = this.state
    const p = { id: persons.length + 1, name: '小王', age: 20 }
    this.setState({ persons: [p, ...persons] })
  }

  render() {
    return (
      <div>
        <h2>展示人员信息</h2>
        <button onClick={this.add}>添加小王</button>
        <h3>使用index作为key</h3>
        <ul>
          {this.state.persons.map((personObj, index) => {
            return (
              <li key={index}>
                {personObj.name}---{personObj.age}
                <input type="text" />
              </li>
            )
          })}
      </div>
    )
  }
}

React 脚手架

创建 React 项目

  • 全局安装 React 脚手架:npm i -g create-react-app
  • 创建项目:create-react-app 项目名称
  • 进入文件夹:cd 项目名称
  • 启动项目:npm start

上述方式已经过时,改用下方命令。详见官方说明

npx create-react-app my-app
cd my-app
npm start

React 脚手架项目架构

public:静态资源文件

  • favicon.icon:网站页签图标
  • index.html:主页面
  • manifest.json :应用加壳(把网页变成安卓/IOS 软件)的配置文件
  • robots.txt :爬虫协议文件

src:源码文件

  • App.css:App 组件的样式
  • App.js:App 组件
  • App.test.js :用于给 App 组件做测试,一般不用
  • index.css:样式
  • index.js :入口文件
  • reportWebVitals.js :页面性能分析文件,需要 web-vitals 库支持
  • setupTests.js :组件单元测试文件,需要 jest-dom 库支持

index.html代码分析

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <!-- %PUBLIC_URL% 代表 public 文件夹的路径 -->
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <!-- 开启理想视口,用于做移动端网页的适配 -->
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <!-- 用于配置浏览器页签+地址栏的颜色(仅支持安卓手机浏览器) -->
    <meta name="theme-color" content="red" />
    <!-- 网站描述 -->
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <!-- 用于指定网页添加到手机主屏幕后的图标 -->
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <!-- 应用加壳时的配置文件 -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <title>React App</title>
  </head>
  <body>
    <!-- 若浏览器不支持 js 则展示标签中的内容 -->
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

样式的模块化

样式的模块化可用于解决样式冲突的问题。该方法比较麻烦,实际开发用的比较少。用 less 就能解决了。

component/Hello 文件下的 index.css 改名为 index.module.css

.title {
  background-color: orange;
}

Hello组件导入样式:

import { Component } from 'react';
import hello from './index.module.css';

export default class Hello extends Component {
  render() {
    return <h2 className={hello.title}>Hello,React!</h2>;
  }
}

TodpList 案例总结

  1. 拆分组件、实现静态组件,注意:classNamestyle的写法
  2. 动态初始化列表,如何确定将数据放在哪个组件的 state 中?
  • 某个组件使用:放在其自身的state
  • 某些组件使用:放在他们共同的父组件state 中,即状态提升
  1. 关于父子之间通信:
  • 父传子:直接通过props 传递
  • 子传父:父组件通过props 给子组件传递一个函数,子组件调用该函数
// 父组件
class Father extends Component {
  state: {
    todos: [{ id: '001', name: '吃饭', done: true }],
    flag: true,
  };

  addTodo = (todo) => {
    const { todos } = this.state;
    const newTodos = [todo, ...todos];
    this.setState({ todos: newTodos });
  };

  render() {
    return <List todos={this.state.todos} addTodo={this.addTodo} />;
  }
}

// 子组件
class Son extends Component {
  // 由于 addTodo 是箭头函数,this 指向父组件实例对象,因此子组件调用它相当于父组件实例在调用
  handleClick = () => {
    this.props.addTodo({ id: '002', name: '敲代码', done: false });
  };

  render() {
    return <button onClick={this.handleClick}>添加</button>;
  }
}
  1. 注意defaultCheckedchecked 的区别,类似的还有:defaultValuevalue

  2. 状态在哪里,操作状态的方法就在哪里

完整案例 ✈️

React 网络请求

React脚手架配置代理

方法一: 在 package.json 文件中进行配置:

"proxy": "http://localhost:5000" //服务器地址
  • 优点:配置简单,前端请求资源可不加前缀
  • 缺点:不能配置多个代理
  • 工作方式:当请求了 3000 端口号(本机)不存在的资源时,就会把请求转发给 5000 端口号服务器

方法二: 在 src 目录下创建代理配置文件 setupProxy.js ,进行配置:

const proxy = require('http-proxy-middleware')

module.exports = function (app) {
  app.use(
    //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
    proxy('/api1', {
      //配置转发目标地址(能返回数据的服务器地址)
      target: 'http://localhost:5000',
      //控制服务器接收到的请求头中host字段的值
      /*
      changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
      changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
      changeOrigin默认值为false,但一般将changeOrigin改为true
      */
      changeOrigin: true,

      //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
      pathRewrite: { '^/api1': '' },
    }),
    proxy('/api2', {
      target: 'http://localhost:5001',
      changeOrigin: true,
      pathRewrite: { '^/api2': '' },
    })
  )
}
  • 优点:可以配置多个代理,可以灵活的控制请求是否走代理
  • 缺点:配置繁琐,前端请求资源时必须加前缀

消息订阅 - 发布机制

即 React 中兄弟组件或任意组件之间的通信方式。

使用的工具库:PubSubJS

下载安装 PubSubJS :npm install pubsub-js --save

基础用法:

import PubSub from 'pubsub-js'

// 订阅消息
var token = PubSub.subscribe('topic', (msg, data) => {
  console.log(msg, data)
})

// 发布消息
PubSub.publish('topic', 'hello react')

// 取消订阅
PubSub.unsubscribe(token)

github搜索用户案例

  1. 配置代理服务器来请求github【nodejs】
//server.js
const express = require('express');
const axios = require('axios');
const app = express();

// 设置代理,以便可以从服务器端直接请求GitHub的API
app.use('/search/users', (req, res) => {
  const query = req.query.q;
  const url = `https://api.github.com/search/users?q=${query}`;
  axios
    .get(url)
    .then((response) => {
      res.json(response.data);
    })
    .catch((error) => {
      console.error('Error fetching data: ', error);
      res.status(500).send('Server error');
    });
});
app.get('/search/users2', (request, response) => {
  // 随机生成100条用户头像数据
  const users = Array.from({ length: 100 }, (_, i) => ({
    id: i + 1,
    avatar: `https://picsum.photos/200/300?random=${i}`,
  }));
  response.send(users);
});

app.listen(5000, (err) => {
  if (!err) {
    console.log(`Server running on 5000`);
  }
});
  1. 配置代理解决跨域问题
//setupProxy.js

Github 搜索框案例知识点总结

  1. 设计状态时要考虑全面,例如带有网络请求的组件,要考虑请求失败怎么办。
  2. ES6 知识点:解构赋值 + 重命名
let obj = { a: { b: 1 } }

//传统解构赋值
const { a } = obj

//连续解构赋值
const {
  a: { b },
} = obj

//连续解构赋值 + 重命名
const {
  a: { b: value },
} = obj
  1. 消息订阅与发布机制
  • 先订阅,再发布(隔空对话)
  • 适用于任意组件间通信
  • 要在 componentWillUnmount 钩子中取消订阅
  1. fetch 发送请求(关注分离的设计思想)
try {
  // 先看服务器是否联系得上
  const response = await fetch(`/api1/search/users2?q=${keyWord}`)
  // 再获取数据
  const data = await response.json()
  console.log(data)
} catch (error) {
  console.log('请求出错', error)
}

React Router5

路由的理解

路由的基本使用

路由组件和一般组件

NavLink的使用

Switch的使用

解决多级路径刷新页面样式丢失的问题

路由的严格匹配和模糊匹配

Redirectd的使用

嵌套路由

路由传参

编程式导航

withRouter 的使用

BrowserRouter 和 HashRouter

React UI 组件库

Redux

React Redux

React 扩展

React Router6

空文件

简介

react学习笔记 展开 收起
JavaScript 等 3 种语言
取消

发行版

暂无发行版

贡献者

全部

近期动态

不能加载更多了
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/fekers/react-study.git
git@gitee.com:fekers/react-study.git
fekers
react-study
React-Study
master

搜索帮助