React 基础学习笔记

React 基础学习笔记 

  • 参考:王红元老师的React

React的特点 – 声明式编程

  • 声明式编程:
    • 声明式编程是目前整个大前端开发的模式:Vue、React、Flutter、SwiftUI;
    • 它允许我们只需要维护自己的状态,当状态改变时,React可以根据最新的状态去渲染我们的UI界面;

 React开发依赖

  • 开发React必须依赖三个库:
    • react:包含react所必须的核心代码
    • react-dom:react渲染在不同平台所需要的核心代码
    • babel:将jsx转换成React代码的工具
  • 第一次接触React会被它繁琐的依赖搞蒙,对于Vue来说,我们只是依赖一个vue.js文件即可,但是react居然要依赖三个库。
    • 其实呢,这三个库是各司其职的,目的就是让每一个库只单纯做自己的事情:
    • 在React的0.14版本之前是没有react-dom这个概念的,所有功能都包含在react里。
    • 为什么要进行拆分呢?原因就是react-native。
    • react包中包含了react和react-native所共同拥有的核心代码。
  • react-dom针对web和native所完成的事情不同:
    • web端:react-dom会讲jsx最终渲染成真实的DOM,显示在浏览器中
    • native端:react-dom会讲jsx最终渲染成原生的控件(比如Android中的Button,iOS中的UIButton)。

引入React依赖

  • 添加这三个依赖: 
    • 方式一:直接CDN引入 
    • 方式二:下载后,添加本地依赖
    • 方式三:通过npm管理 
  • 暂时我们直接通过CDN引入,来演练下面的示例程序:
    • 这里有一个crossorigin的属性,这个属性的目的是为了拿到跨域脚本的错误信息
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

Hello World

  • 第一步:在界面上通过React显示一个Hello World
  • 注意:这里我们编写React的script代码中,必须添加 type="text/babel",作用是可以让babel解析jsx的语法

  • ReactDOM.render函数:
    • 参数一:传递要渲染的内容,这个内容可以是HTML元素,也可以是React的组件
      • 这里我们传入了一个h2元素,后面我们就会使用React组件
    • 参数二:将渲染的内容,挂载到哪一个HTML元素上
      • 这里我们已经提定义一个id为app的div
    • 我们可以通过{}语法来引入外部的变量或者表达式
<body>
  <div id="app">
  </div>
  <!-- 添加React的依赖 -->
  <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
  <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
  <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

  <!-- 开始开发 -->
  <!-- 注意事项: 使用jsx, 并且希望script中的jsx的代码被解析, 必须在script标签中添加一个属性  -->
  <!-- jsx特点: 多个标签最外层(根)只能有一个标签 -->
  <script type="text/babel">
    let message = "Hello World";

    function btnClick() {
      message = "Hello React";
      console.log(message);
      render();
    }

    // <h2></h2>: jsx代码
    function render() {
      ReactDOM.render(
        <div>
          <h2>{message}</h2>
          <button onClick={btnClick}>改变文本</button>
        </div>,
        document.getElementById("app")
      );
    }

    render();
  </script>
</body>

Hello React – 组件化开发

  • 可以先将之前的业务逻辑封装到一个组件中,然后传入到 ReactDOM.render 函数中的第一个参数;
  • 这里我们暂时使用类的方式封装组件:
    • 1.定义一个类类名大写,组件的名称是必须大写的,小写会被认为是HTML元素),继承自React.Component
    • 2.实现当前组件的render函数
      • render当中返回的jsx内容,就是之后React会帮助我们渲染的内容

组件化 - 数据依赖

  • 在组件中的数据,我们可以分成两类:
    • 参与界面更新的数据:当数据变量时,需要更新组件渲染的内容
    • 不参与界面更新的数据:当数据变量时,不需要更新将组建渲染的内容
  • 参与界面更新的数据我们也可以称之为是参与数据流,这个数据是定义在当前对象的state中
    • 我们可以通过在构造函数中 this.state = {定义的数据}
    • 当我们的数据发生变化时,我们可以调用 this.setState 来更新数据,并且通知React进行update操作
      • 在进行update操作时,就会重新调用render函数,并且使用最新的数据,来渲染界面。

组件化 – 事件绑定

  • 组件化问题二:事件绑定中的this
  • 在类中直接定义一个函数,并且将这个函数绑定到html原生的onClick事件上,当前这个函数的this指向的是谁呢?
  • 默认情况下是undefined
    • 很奇怪,居然是undefined;
    • 因为在正常的DOM操作中,监听点击,监听函数中的this其实是节点对象(比如说是button对象);
    • 这次因为React并不是直接渲染成真实的DOM,我们所编写的button只是一个语法糖,它的本质React的Element对象;
    • 那么在这里发生监听的时候,react给我们的函数绑定的this,默认情况下就是一个undefined;
  • 我们在绑定的函数中,可能想要使用当前对象,比如执行 this.setState 函数,就必须拿到当前对象的this
  • 我们就需要在传入函数时,给这个函数直接绑定this
  • 类似于下面的写法: <button onClick={this.changeText.bind(this)}>改变文本</button>

 Hello-world 组件完整示例

<body>
  <div id="app">
  </div>
  <!-- 添加React的依赖 -->
  <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
  <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
  <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
  <!-- 开始开发 -->
  <script type="text/babel">
    // 封装App组件
    class App extends React.Component {
      constructor() {
        super();
        // this.message = "Hello World";
        this.state = {
          message: "Hello World"
        }
      }
      render() {
        return (
          <div>
            <h2>{this.state.message}</h2>
            <button onClick={this.btnClick.bind(this)}>改变文本</button>
          </div>
        )
      }
      btnClick() {
        // this.message = "Hello React";
        // this.state.message = "Hello React";
        // console.log(this.state);
        this.setState({
          message: "Hello React"
        })
      }
    }
    // 渲染组件
    ReactDOM.render(<App/>, document.getElementById("app"));
  </script>
</body>

电影列表展示

<body>
  <div id="app"></div>
  <!-- 1.引入依赖 -->
  <script src="../react/react.development.js"></script>
  <script src="../react/react-dom.development.js"></script>
  <script src="../react/babel.min.js"></script>

  <!-- 2.编写React代码 -->
  <script type="text/babel">
    class App extends React.Component {
      constructor() {
        super();

        this.state = {
          message: "Hello World",
          movies: ["大话西游", "盗梦空间", "星际穿越", "流浪地球"]
        }
      }

      render() {
        const liArray = [];
        for (let movie of this.state.movies) {
          liArray.push(<li>{movie}</li>);
        }

        return (
          <div>
            <h2>电影列表1</h2>
            <ul>
              {liArray}
            </ul>

            <h2>电影列表2</h2>
            <ul>
              {
                this.state.movies.map((item) => {
                  return <li>{item}</li>
                })
              }
            </ul>
          </div>
        )
      }
    }

    ReactDOM.render(<App />, document.getElementById("app"));
  </script>

计数器案例

 <script type="text/babel">
    class App extends React.Component {
      constructor(pops) {
        super(props);

        this.state = {
          counter: 0
        }
      }

      render() {
        return (
          <div>
            <h2>当前计数: {this.state.counter}</h2>
            <button onClick={this.increment.bind(this)}>+1</button>
            <button onClick={this.decrement.bind(this)}>-1</button>
            <img src="" alt=""/>
          </div>
        )
      }

      increment() {
        this.setState({
          counter: this.state.counter + 1
        })
      }

      decrement() {
        this.setState({
          counter: this.state.counter - 1
        })
      }
    }

    ReactDOM.render(<App/>, document.getElementById("app"));
  </script>

认识JSX

  • 这段element变量的声明右侧赋值的标签语法是什么呢?
    • 它不是一段字符串(因为没有使用引号包裹),它看起来是一段HTML原生,但是我们能在js中直接给一个变量赋值html吗?
    • 其实是不可以的,如果我们讲 type="text/babel" 去除掉,那么就会出现语法错误;
    • 它到底是什么呢?其实它是一段jsx的语法;
  • JSX是什么?
    • JSX是一种JavaScript的语法扩展(eXtension),也在很多地方称之为JavaScript XML,因为看起就是一段XML语法;
    • 它用于描述我们的UI界面,并且其完成可以和JavaScript融合在一起使用;
    • 它不同于Vue中的模块语法,你不需要专门学习模块语法中的一些指令(比如v-for、v-if、v-else、v-bind);

为什么React选择了JSX

  • React认为渲染逻辑本质上与其他UI逻辑存在内在耦合
    • 比如UI需要绑定事件(button、a原生等等);
    • 比如UI中需要展示数据状态,在某些状态发生改变时,又需要改变UI;
    • 他们之间是密不可分,所以React没有讲标记分离到不同的文件中,而是将它们组合到了一起,这个地方就是组件(Component);
  • JSX的书写规范:
    • JSX的顶层只能有一个根元素,所以我们很多时候会在外层包裹一个div原生(或者使用后面我们学习的Fragment);
    • 为了方便阅读,我们通常在jsx的外层包裹一个小括号(),这样可以方便阅读,并且jsx可以进行换行书写;
    • JSX中的标签可以是单标签,也可以是双标签;
    • 注意:如果是单标签,必须以/>结尾

JSX的使用

jsx中的注释

JSX嵌入变量

jsx嵌入表达式

JSX嵌入表达式
  • 运算表达式
  • 三元运算符
  • 执行一个函数
<script type="text/babel">
    class App extends React.Component {
      constructor(props) {
        super(props);

        this.state = {
          firstname: "kobe",
          lastname: "bryant",
          isLogin: false
        }
      }

      render() {
        const { firstname, lastname, isLogin } = this.state;

        return (
          <div>
            {/*1.运算符表达式*/}
            <h2>{ firstname + " " + lastname }</h2>
            <h2>{20 * 50}</h2>

            {/*2.三元表达式*/}
            <h2>{ isLogin ? "欢迎回来~": "请先登录~" }</h2>

            {/*3.进行函数调用*/}
            <h2>{this.getFullName()}</h2>
          </div>
        )
      }

      getFullName() {
        return this.state.firstname + " " + this.state.lastname;
      }
    }

    ReactDOM.render(<App/>, document.getElementById("app"));
  </script>

jsx绑定属性

  • 比如元素都会有title属性
  • 比如img元素会有src属性
  • 比如a元素会有href属性
  • 比如元素可能需要绑定class
  • 比如原生使用内联样式style
<body>
  <div id="app"></div>
  <script type="text/babel">
    function getSizeImage(imgUrl, size) {
      return imgUrl + `?param=${size}x${size}`
    }

    class App extends React.Component {
      constructor(props) {
        super(props);

        this.state = {
          title: "标题",
          imgUrl: "http://p2.music.126.net/L8IDEWMk_6vyT0asSkPgXw==/109951163990535633.jpg",
          link: "http://www.baidu.com",
          active: true
        }
      }

      render() {
        const { title, imgUrl, link, active } = this.state;
        return (
          <div>
            {/* 1.绑定普通属性 */}
            <h2 title={title}>我是标题</h2>
            <img src={getSizeImage(imgUrl, 140)} alt=""/>
            <a href={link} target="_blank">百度一下</a>

            {/* 2.绑定class */}
            <div className="box title">我是div元素</div>
            <div className={"box title " + (active ? "active": "")}>我也是div元素</div>
            <label htmlFor=""></label>

            {/* 3.绑定style */}
            <div style={{color: "red", fontSize: "50px"}}>我是div,绑定style属性</div>
          </div>
        )
      }
    }

    ReactDOM.render(<App />, document.getElementById("app"));
  </script>

React事件绑定

  • React 事件的命名采用小驼峰式(camelCase),而不是纯小写;
    • 我们需要通过{}传入一个事件处理函数,这个函数会在事件发生时被执行
  • this的绑定问题
    • 原因是btnClick函数并不是我们主动调用的,而且当button发生改变时,React内部调用了btnClick函数;
    • 而它内部调用时,并不知道要如何绑定正确的this;
  • 如何解决this的问题呢?
    • 方案一:bind给btnClick显示绑定this
    • 方案二:使用 ES6 class fields 语法
    • 方案三:事件监听时传入箭头函数(推荐)
<script type="text/babel">
  class App extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        message: "你好啊",
        counter: 100
      }

      this.btnClick = this.btnClick.bind(this);
    }

    render() {
      return (
        <div>
          {/* 1.方案一: bind绑定this(显示绑定) */}
          <button onClick={this.btnClick}>按钮1</button>
          <button onClick={this.btnClick}>按钮2</button>
          <button onClick={this.btnClick}>按钮3</button>

          {/* 2.方案二: 定义函数时, 使用箭头函数 */}
          <button onClick={this.increment}>+1</button>

          {/* 2.方案三(推荐): 直接传入一个箭头函数, 在箭头函数中调用需要执行的函数*/}
          <button onClick={() => { this.decrement("why") }}>-1</button>
        </div>
      )
    }

    btnClick() {
      console.log(this.state.message);
    }

    // increment() {
    //   console.log(this.state.counter);
    // }
    // 箭头函数中永远不绑定this
    // ES6中给对象增加属性: class fields
    increment = () => {
      console.log(this.state.counter);
    }

    decrement(name) {
      console.log(this.state.counter, name);
    }
  }

  ReactDOM.render(<App/>, document.getElementById("app"));
</script>

事件参数传递

<script type="text/babel">
  class App extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        movies: ["大话西游", "海王", "流浪地球", "盗梦空间"]
      }
      this.btnClick = this.btnClick.bind(this);
    }
    render() {
      return (
        <div>
          <button onClick={this.btnClick}>按钮</button>
          
          <ul>
            {
              this.state.movies.map((item, index, arr) => {
                return (
                  <li className="item" 
                      onClick={ e => { this.liClick(item, index
                      title="li">
                    {item}
                  </li>
                )
              })
            }
          </ul>
        </div>
      )
    }
    btnClick(event) {
      console.log("按钮发生了点击", event);
    }
    liClick(item, index, event) {
      console.log("li发生了点击", item, index, event);
    }
  }
  ReactDOM.render(<App/>, document.getElementById("app"));
</script>

React条件渲染

<script type="text/babel">
  class App extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        isLogin: true
      }
    }
    render() {
      const { isLogin } = this.state;
      // 1.方案一:通过if判断: 逻辑代码非常多的情况
      let welcome = null;
      let btnText = null;
      if (isLogin) {
        welcome = <h2>欢迎回来~</h2>
        btnText = "退出";
      } else {
        welcome = <h2>请先登录~</h2>
        btnText = "登录";
      }
      return (
        <div>
          <div>我是div元素</div>
          {welcome}
          {/* 2.方案二: 三元运算符 */}
          <button onClick={e => this.loginClick()}>
          <hr />
          <h2>{isLogin ? "你好啊, coderwhy": null}<
          {/* 3.方案三: 逻辑与&& */}
          {/* 逻辑与: 一个条件不成立, 后面的条件都
          <h2>{ isLogin && "你好啊, coderwhy" }</h2
          { isLogin && <h2>你好啊, coderwhy</h2> }
        </div>
      )
    }
    loginClick() {
      this.setState({
        isLogin: !this.state.isLogin
      })
    }
  }
  ReactDOM.render(<App />, document.getElementById(
</script>

条件渲染-v-show效果

<script type="text/babel">
  class App extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        isLogin: true
      }
    }
    render() {
      const { isLogin} = this.state;
      const titleDisplayValue = isLogin ? "block": "none";
      return (
        <div>
          <button onClick={e => this.loginClick()}>{isLogin ? "退出": "登录"}</button>
          <h2 style={{display: titleDisplayValue}}>你好啊, coderwhy</h2>
        </div>
      )
    }
    loginClick() {
      this.setState({
        isLogin: !this.state.isLogin
      })
    }
  }
  ReactDOM.render(<App />, document.getElementById("app"));
</script>
  • 示例2:
<script type="text/babel">
  class App extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        names: ["abc", "cba", "nba", "mba", "dna"],
        numbers: [110, 123, 50, 32, 55, 10, 8, 333]
      }
    }
    render() {
      return (
        <div>
          <h2>名字列表</h2>
          <ul>
            {
              this.state.names.map(item => {
                return <li>{item}</li>
              })
            }
          </ul>
          <h2>数字列表(过滤1)</h2>
          <ul>
            {
              this.state.numbers.filter(item => {
                return item >= 50;
              }).map(item => {
                return <li>{item}</li>
              })
            }
          </ul>
          <h2>数字列表(过滤2)</h2>
          <ul>
            {
              this.state.numbers.filter(item => item >= 50).map(item => <l
            }
          </ul>
          <h2>数字列表(截取)</h2>
          <ul>
            {
              this.state.numbers.slice(0, 4).map(item => {
                return <li>{item}</li>
              })
            }
          </ul>
        </div>
      )
    }
  }
  ReactDOM.render(<App />, document.getElementById("app"));
</script>

JSX的本质

  • 实际上,jsx 仅仅只是 React.createElement(component, props, ...children) 函数的语法糖。
    • 所有的jsx最终都会被转换成React.createElement的函数调用
createElement需要传递三个参数:
  • 参数一:type
    • 当前ReactElement的类型;
    • 如果是标签元素,那么就使用字符串表示 “div”;
    • 如果是组件元素,那么就直接使用组件的名称;
  • 参数二:config
    • 所有jsx中的属性都在config中以对象的属性和值的形式存储
  • 参数三:children
    • 存放在标签中的内容,以children数组的方式进行存储;
    • 当然,如果是多个元素呢?React内部有对它们进行处理.

Babel官网查看

  • 我们知道默认jsx是通过babel帮我们进行语法转换的,所以我们之前写的jsx代码都需要依赖babel。
  • 可以在babel的官网中快速查看转换的过程:https://babeljs.io/repl/#?presets=react

直接编写jsx代码

  • 我们自己来编写React.createElement代码:
    • 我们就没有通过jsx来书写了,界面依然是可以正常的渲染。
    • 另外,在这样的情况下,你还需要babel相关的内容吗?不需要了
      • 所以,type="text/babel"可以被我们删除掉了;
      • 所以,<script src="../react/babel.min.js"></script>可以被我们删除掉了

虚拟DOM的创建过程

  • 我们通过 React.createElement 最终创建出来一个 ReactElement对象:
  • 这个ReactElement对象是什么作用呢?React为什么要创建它呢?
    • 原因是React利用ReactElement对象组成了一个JavaScript的对象树;
    • JavaScript的对象树就是大名鼎鼎的虚拟DOM(Virtual DOM);
  • 如何查看ReactElement的树结构呢?
    • 我们可以将之前的jsx返回结果进行打印;
    • 注意下面代码中我打jsx的打印;
  • 而ReactElement最终形成的树结构就是Virtual DOM;

为什么使用虚拟DOM

  • 为什么要采用虚拟DOM,而不是直接修改真实的DOM呢?
    • 很难跟踪状态发生的改变:原有的开发模式,我们很难跟踪到状态发生的改变,不方便针对我们应用程序进行调试;
    • 操作真实DOM性能较低:传统的开发模式会进行频繁的DOM操作,而这一的做法性能非常的低;
  • DOM操作性能非常低:
    • 首先,document.createElement本身创建出来的就是一个非常复杂的对象;
    • https://developer.mozilla.org/zh-CN/docs/Web/API/Document/createElement
    • 其次,DOM操作会引起浏览器的回流和重绘,所以在开发中应该避免频繁的DOM操作;

声明式编程

  • 虚拟DOM帮助我们从命令式编程转到了声明式编程的模式
  • React官方的说法:Virtual DOM 是一种编程理念。
    • 在这个理念中,UI以一种理想化或者说虚拟化的方式保存在内存中,并且它是一个相对简单的JavaScript对象
    • 我们可以通过ReactDOM.render虚拟DOM 和 真实DOM同步起来,这个过程中叫做协调(Reconciliation)
  • 这种编程的方式赋予了React声明式的API:
    • 你只需要告诉React希望让UI是什么状态;
    • React来确保DOM和这些状态是匹配的;
    • 你不需要直接进行DOM操作,只可以从手动更改DOM、属性操作、事件处理中解放出来;

阶段练习示例

  •  主要代码:
<script type="text/babel">
  class App extends React.Component {
    constructor(props) {
      super(props);

      this.state = {
        books: [
          {
            id: 1,
            name: '《算法导论》',
            date: '2006-9',
            price: 85.00,
            count: 2
          },
          {
            id: 2,
            name: '《UNIX编程艺术》',
            date: '2006-2',
            price: 59.00,
            count: 1
          },
          {
            id: 3,
            name: '《编程珠玑》',
            date: '2008-10',
            price: 39.00,
            count: 1
          },
          {
            id: 4,
            name: '《代码大全》',
            date: '2006-3',
            price: 128.00,
            count: 1
          },
        ]
      }
    }

    renderBooks() {
      return  (
        <div>
          <table>
            <thead>
              <tr>
                <th></th>
                <th>书籍名称</th>
                <th>出版日期</th>
                <th>价格</th>
                <th>购买数量</th>
                <th>操作</th>
              </tr>
            </thead>
            <tbody>
              {
                this.state.books.map((item, index) => {
                  return (
                    <tr>
                      <td>{index+1}</td>
                      <td>{item.name}</td>
                      <td>{item.date}</td>
                      <td>{formatPrice(item.price)}</td>
                      <td>
                        <button disabled={item.count <= 1} onClick={e => this.changeBookCount(index, -1)}>-</button>
                        <span className="count">{item.count}</span>
                        <button onClick={e => this.changeBookCount(index, 1)}>+</button>
                      </td>
                      <td><button onClick={e => this.removeBook(index)}>移除</button></td>
                    </tr>
                  )
                })
              }
            </tbody>
          </table>
          <h2>总价格: {this.getTotalPrice()}</h2>
        </div>
      )
    }

    renderEmptyTip() {
      return <h2>购物车为空~</h2>
    }

    render() {
      return this.state.books.length ? this.renderBooks(): this.renderEmptyTip();
    }

    changeBookCount(index, count) {
      const newBooks = [...this.state.books];
      newBooks[index].count += count;
      this.setState({
        books: newBooks
      })
    }

    removeBook(index) {
      // React中设计原则: state中的数据的不可变性;
      this.setState({
        books: this.state.books.filter((item, indey) => index != indey)
      })
    }

    getTotalPrice() {
      // 1.for循环的方式
      // let totalPrice = 0;
      // for (let item of this.state.books) {
      //   totalPrice += item.price * item.count;
      // }
      // return formatPrice(totalPrice);

      // 2.filter/map/reduce(归纳为)
      // 回调函数的参数:
      // 参数一: 上一次回调函数的结果(第一次没有上一次函数的回调函数的结果, 使用初始化值)
      const totalPrice = this.state.books.reduce((preValue, item) => {
        return preValue + item.count * item.price;
      }, 0);

      return formatPrice(totalPrice);
    }
  }

  ReactDOM.render(<App/>, document.getElementById("app"));
</script>

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值