React 基础内容

一. 使用React编写todolist

1. 占位符Fragment;

先从下面图片开始了解本节的目录结构、代码、效果:

目录结构:

代码:

index.js

TodoList.js

效果:

上图中红色方框中的dom结构是用一个div包裹的,但是有时候我们在实现flex布局的时候该div包裹会影响我们的样式。React提供了一个占位符Fragment。我们引入该占位符,同时将最外层的div包裹替换成Fragment即可。

 

修改后的代码:

TodoList.js

 

效果:

 


二. React中的响应式设计思想和事件绑定

        如果用原生js或jquery来做todolist的话,我们一般是获取输入框的value值,然后通过document.getElementById获取列表区域,然后将value值挂载到列表区域。

        React与上面直接操作dom的形式不同。它是一个响应式的框架,通过操作数据,React会感知到数据的变化,自动地帮你去生成dom。所以写React项目的时候,我们只需要关注数据层的变化。

 

1. Constructor

        在js中,一个对象或者类就有一个constructor构造函数。在React中,当我们去创建或者使用TodoList这个组件的时候,constructor这个构造函数时优于别的任何函数的,自动的最先被执行的一个函数。所以我们可以在该构造函数里定义数据。

  

 

2. 如何双向绑定

以todolist的input框的value值为例,我们将它和todolist状态中的inputValue进行绑定。

<input value={this.state.inputValue}/>

想在JSX语法中使用js的变量(如this.state.inputValue),我们需要用大括号将其括起来。

 

3. React事件绑定

以监听input框的change事件为例,原生js是onchange, 在React中应该写成onChange。

TodoList.js:

import React, { Component, Fragment } from 'react';

class TodoList extends Component {
  constructor(props) {
    //固定写法,意思是TodoList是继承自React的component这个组件,我们这要调用一次父类的构造函数
    super(props);
    //React的数据需要定义在状态里面,下面的state就是表示组件的状态
    this.state = {
      inputValue: 'hahah',  //input框的value值
      list: []        //数组中的每一项
    }
  }

  render() {
    return (   //jsx语法要求render函数返回的内容外层必须用一个标签包裹
      <Fragment>
        <div>
          <input 
            value={this.state.inputValue}
            onChange={this.handleInputChange}
          />
          <button>提交</button>
        </div>
        <ul>
          <li>学英语</li>
          <li>学React</li>
        </ul>
      </Fragment>
    )
  }

  handleInputChange(e) {
    console.log(e.target.value);   //控制台会打印出第一次修改后的value值,但是input框内容未改变
  }
}

export default TodoList;   //导出TodoList组件

按照常理,我们想input框的value跟着改变的话,加入如下代码即可:

handleInputChange(e) {
    this.state.inputValue = e.target.value;   //Cannot read property 'state' of undefined
}

实际上,上面的this并不是指向TodoList这个组件,打印this结果是undefined。我们可以通过bind来更改this的指向。

<input 
            value={this.state.inputValue}
            onChange={this.handleInputChange.bind(this)}
/>

handleInputChange(e) {
  this.state.inputValue = e.target.value;  //此时this指向的是该组件,但是还是不能改变输入框的值
}

//原因是React不能直接通过this.state来改变state里面的值,它提供了一个方法:this.setState({})

最终解决办法:

handleInputChange(e) {
    this.setState({
      inputValue: e.target.value
    })
}

 


三. 实现TodoList新增删除功能

import React, { Component, Fragment } from 'react';

class TodoList extends Component {
  constructor(props) {
    //固定写法,意思是TodoList是继承自React的component这个组件,我们这要调用一次父类的构造函数
    super(props);
    //React的数据需要定义在状态里面,下面的state就是表示组件的状态
    this.state = {
      inputValue: '',  //input框的value值
      list: []        //数组中的每一项
    }
  }

  render() {
    return (   //jsx语法要求render函数返回的内容外层必须用一个标签包裹
      <Fragment>
        <div>
          <input 
            value={this.state.inputValue}
            onChange={this.handleInputChange.bind(this)}
          />
          <button onClick={this.handleBtnClick.bind(this)}>提交</button>
        </div>
        <ul>
          {
            this.state.list.map((item, index) => {
              return (
                <li 
                  key={index}
                  onClick={this.handleItemDelete.bind(this, index)}
                >{item}</li>
              )
            })
          }
        </ul>
      </Fragment>
    )
  }

  //输入框value发生改变
  handleInputChange(e) {
    this.setState({
      inputValue: e.target.value
    })
  }

  //点击提交将value加入列表项
  handleBtnClick() {
    this.setState({
      //es6的展开运算符,将this.state.list该数组展开成参数列表的形式(如:[...[1, 2]] => [1, 2])
      list: [...this.state.list, this.state.inputValue],
      inputValue: ''
    })
  }

  //点击列表项删除
  handleItemDelete(index) {
    //immutable, React不允许我们直接对state做任何修改
    const list = this.state.list;
    list.splice(index, 1);
    this.setState({
      list: list
    })
  }
}

export default TodoList;   //导出TodoList组件

 

四、JSX语法细节补充

 

1、如何在JSX语法中编辑注释。

我们只需要将注释用花括号包裹即可。注意:单行注释应该重起一行。

 

2、在React中引入外部样式。

新建样式文件style.css

在TodoList.js中引入

给input框添加类名

结果显示样式发生了改变,但是控制台报错:

这是因为React为了避免将input中的class与类相混淆,不推荐我们使用class来表示元素的类名,推荐我们使用className来代替。

将上面的class修改成className,样式发生了改变且控制台不报错。

 

3、对输入框输出内容进行转义。

对于上图的结果,我们希望输出结果中的h1被解析转义出来。代码需进行如下修改:

最终结果如下所示:

 

4、点击标签左边文字使光标聚焦在输入框。

在HTML中label标签的作用是扩大输入框的点击区域。我们需要实现一个功能:点击输入内容,让光标聚焦在输入框。

添加如下代码实现:

效果如下:

但是控制台报错:

原因和上面一样,也是React为了避免label标签中的for属性和js的for语句混淆,不推荐使用for来关联input的id。使用htmlFor来替换for。

效果一样,同时控制台也不报错。

 


五. 拆分组件与组件之间的传值

1. 将TodoList这个组件拆分成两个组件

首先在src目录下创建一个TodoItem.js组件

输入内容如下:

在TodoList中引入该组件:

修改TodoList中的JSX:

注意:应该用一个元素包裹TodoItem与注释,否则会报错。

结果界面:

显然这不是我们想要的结果,这是因为我们将TodoItem里面内容写死了。要实现我们的功能,我们需要父组件向子组件传值

 

2. 父组件向子组件传值

通过属性的方式进行传值,子组件通过this.props接收。具体例子如下:

通过属性传值:

通过this.props接收:

结果界面:

 

3. 子组件操作父组件的数据

接下来实现点击每个列表项自动删除的功能:

首先子组件需要获取被点击那一项的index值:

接下来我们需要将该值传递给父组件的handleItemDelete方法,删除数组中对应index的那一项。

父组件:

子组件:

此时,当我们点击列表项的时候报错

原因是因为我们子组件中调用this.props.deleteItem方法实际上就是调用父组件的this.handleItemDelete方法,但是这里的this指向是子组件,我们需要强制改变this的指向,使它指向父组件。

最后功能成功实现了,也没有报错。

 


六. TodoList代码优化

1. TodoItem.js代码优化

利用ES6的解构赋值对代码优化:

优化前:

优化后:

 

2. TodoList.js代码优化

主要优化部分:

      1. 在constructor部分改变this的指向

      2.将JSX语法中的逻辑部分用一个方法来表示

      3.使用新版React推荐的setState语法(不直接返回一个对象,而是返回一个函数,函数内再return一个对象)

      4.解决使用循环带来的key值问题

优化前:

import React, { Component, Fragment } from 'react';
import './style.css';
import TodoItem from './TodoItem';

class TodoList extends Component {
  constructor(props) {
    //固定写法,意思是TodoList是继承自React的component这个组件,我们这要调用一次父类的构造函数
    super(props);
    //React的数据需要定义在状态里面,下面的state就是表示组件的状态
    this.state = {
      inputValue: '',  //input框的value值
      list: []        //数组中的每一项
    }
  }

  render() {
    return (
      <Fragment>
        <div>
          <label htmlFor="insert">输入内容</label>
          <input
            id="insert" 
            value={this.state.inputValue}
            onChange={this.handleInputChange.bind(this)}
            className='input'
          />
          <button onClick={this.handleBtnClick.bind(this)}>提交</button>
        </div>
        <ul>
          {
            this.state.list.map((item, index) => {
              return (
                <div>
                  <TodoItem 
                    content={item}
                    index={index}
                    deleteItem={this.handleItemDelete.bind(this)}
                  />
                </div>
              )
            })
          }
        </ul>
      </Fragment>
    )
  }

  //输入框value发生改变
  handleInputChange(e) {
    this.setState({
      inputValue: e.target.value
    })
  }

  //点击提交将value加入列表项
  handleBtnClick() {
    this.setState({
      //es6的展开运算符,将this.state.list该数组展开成参数列表的形式(如:[...[1, 2]] => [1, 2])
      list: [...this.state.list, this.state.inputValue],
      inputValue: ''
    })
  }

  //点击列表项删除
  handleItemDelete(index) {
    //immutable, React不允许我们直接对state做任何修改
    const list = this.state.list;
    list.splice(index, 1);
    this.setState({
      list: list
    })
  }
}

export default TodoList;   //导出TodoList组件

优化后:

import React, { Component, Fragment } from 'react';
import './style.css';
import TodoItem from './TodoItem';

class TodoList extends Component {
  constructor(props) {
    //固定写法,意思是TodoList是继承自React的component这个组件,我们这要调用一次父类的构造函数
    super(props);
    //React的数据需要定义在状态里面,下面的state就是表示组件的状态
    this.state = {
      inputValue: '',  //input框的value值
      list: []        //数组中的每一项
    };
    this.handleInputChange = this.handleInputChange.bind(this);
    this.handleBtnClick = this.handleBtnClick.bind(this);
    this.handleItemDelete = this.handleItemDelete.bind(this);
  }

  render() {
    return (
      <Fragment>
        <div>
          <label htmlFor="insert">输入内容</label>
          <input
            id="insert" 
            value={this.state.inputValue}
            onChange={this.handleInputChange}
            className='input'
          />
          <button onClick={this.handleBtnClick}>提交</button>
        </div>
        <ul>
          {this.getTodoItem()}
        </ul>
      </Fragment>
    )
  }

  //输入框value发生改变
  handleInputChange(e) {
    const value = e.target.value;
    this.setState(() => ({
      inputValue: value
    }));
  }

  //点击提交将value加入列表项
  handleBtnClick() {
    this.setState((prevState) => ({
      //prevState指的是修改数据之前那次数据,该写法可以避免我们有时候不小心改变了state的状态
      list: [...prevState.list, prevState.inputValue],
      inputValue: ''
    }));
  }

  //遍历list,显示每一个item
  getTodoItem() {
    return this.state.list.map((item, index) => {
      return (
        <TodoItem 
          content={item}
          index={index}
          key={index}
          deleteItem={this.handleItemDelete}
        />
      )
    })
  }

  //点击列表项删除
  handleItemDelete(index) {
    this.setState((prevState) => {
      const list = prevState.list;
      list.splice(index, 1);
      return {list};
    })
  }
}

export default TodoList;   //导出TodoList组件

 


七. 围绕React衍生出的一些思考

1. 命令式开发、声明式开发

命令式开发: 我们之前使用的jquery开发都是直接操作dom,我们把这种方式称为命令式开发。就是我们开发的时候,需要告诉页面,你要如何去获取,接着又如何去挂载,这种命令式的方式。

声明式开发:React就是声明式的开发,比如我们要开发一个网页,之前命令式的开发需要一步一步地去指导如何去开发。而React不是这样的,它是面向数据来编程的,我们只需要把数据构建出就可以,React会自动根据数据去构建这个网页。数据就类似一个图纸,React根据图纸会自动地帮你去构建这栋房子。

 

2.可以与其它框架并存

在React项目的入口文件,我们将TodoList这个组件挂载到id等于root的元素下面,我们开发过程中,第三方框架在id等于root的元素之外使用是不会影响到React的,同样React也不会影响其他的框架。

 

3. 组件化

4. 单向数据流

父组件可以传数据给子组件,但是不允许子组件直接改变传递过来的数据。这是因为当该数据被多个组件使用的时候,我们子组件改变了该数据,别的组件使用该数据时也被改变了,而且在出现bug时不容易定位。

 

5.视图层的框架

因为React在开发大型项目的时候,组件树比较复杂,单靠React父子组件传值很麻烦,我们需要引用别的像Redux这样的数据层的框架来辅助我们开发。React只负责解决页面与数据渲染上的一些问题,至于组件之间如何传值,它并不负责。对于Todolist这种小型的项目,借助React内部的传值机制就可以,但是大中型项目,这时远远不够的。

 

6. 函数式编程

我们在React文件中可以看到,React开发都是一个函数一个函数地去编写。这带来了几个好处:

    1. 维护起来容易。函数比较大地时候可以进行拆分,每个函数各司其职。

    2. 方便自动化测试。我们只需要给函数一个输入值,看输出值是否正确,给前端自动化测试带来了很大地便捷性。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值