React.js基础精讲(1)

初学react的TodoList功能代码练习和思路总结

使用React编写TodoList功能

  • 在index.js文件中引入TodoList.js文件TodoList组件时使用的import App from './TodoList';其实省略了.js,这是因为你不写.js的话脚手架工具会自动到目录下去找js后缀的文件(但是你在根目录下创建TodoList.js时不要忘记后缀!

TodoList.js画出框架

  • 先引入React才能使用JSX语法,引入{ Component }才能使我们自定义的组件继承Component:
    1
    import React, { Component } from 'react';/* 快捷键imrc */
  • 自定义的组件一定要继承Component,通过render函数返回组件显示的内容,将组件返回的内容写在return()内(注意是小括号!):
    1
    2
    3
    4
    5
    6
    7
    class TodoList extends Component{
    render(){
    return(

    )
    }
    }
  • 在最后必须使用export default TodoList;导出组件(不导出的话组件在外部是无法引用的
    1
    export default TodoList;
  • 对于render函数来说,4个空格一个缩进太大了,我们可以将它修改为2个空格一个缩进。(修改方法如下)

修改缩进长度

修改缩进步骤+效果

Fragment代替父元素标签

  • 不要忘记在render函数的return()括号中写类似html代码时,只允许存在1个最外层标签,否则报错。
    • 也就是说所有标签都必须包含在1个父元素下
  • 但很多时候我们并不希望他们有父元素,此时可以使用React16提供的占位符Fragment代替父元素标签。
  • 记得先引入Fragment:
    1
    import React, { Component,Fragment } from 'react';
  • 然后可以使用<Fragment>代替<div>作为父元素使用,当你在页面F12时可以发现子元素全部都直接挂靠在了我们指定的index.html的root节点上
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import React, { Component,Fragment } from 'react';/* 快捷键imrc */

    class TodoList extends Component {
    render() {
    return (
    <Fragment>
    <div>
    <input/>
    <button>提交</button>
    </div>
    <ul>
    <li>学习</li>
    <li>做题</li>
    </ul>
    </Fragment>
    )
    }
    }

    export default TodoList;
    效果图
    想要实现输入框中输入的内容被提交以后显示在下方列表中则需要下面讲到的“React中的响应式设计思想和事件绑定”。

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

  • react在英语中就是“响应、做出反应”的意思,也就是说代码中的数据发生变化时会直接反应在页面上
    • 也就是说当数据被修改时会被直接反应在页面上
  • 所以在写react时我们不是去改变DOM,而是去修改数据,数据被修改以后会自己反应在DOM上。
  • 总结:我们不需要去关注DOM方面的操作,只需要考虑数据层的操作即可,数据改变视图。

实现功能

  • 在输入框中键入时页面输入框可同步变化数据内容。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import React, { Component, Fragment } from 'react';/* 快捷键imrc */

class TodoList extends Component {
constructor(props) {/* 构造函数constructor带有参数props */
super(props);/* 继承父类Component的props */
this.state = {/* 所有的数据都需要保存在状态里(即this.state) */
/* inputValue用于存放输入框内容 */
inputValue: "",
/* list列表用于存放输入框下方数据 */
list: []
}
}

render() {
return (
<Fragment>
<div>
{/*
注意:如果不绑定onChange事件你不管输入什么框内都不会有所变化,
因为如果是原生html,则可理解为input的value = e.target.value,自己展示自己的变化
而现在我们将value = 我们自己设定的this.state.inputValue,可inputValue如果不手动更新是不变的
区分react中 defaultValue属性 和 value
*/}
<input
value = { this.state.inputValue }
onChange = { this.handleInputChange.bind(this) }
/>
<button>提交</button>
</div>
<ul>
<li>学习</li>
<li>做题</li>
</ul>
</Fragment>
)
}
handleInputChange(e) {/* 传入一个事件对象e */
/* e.target:事件对象e对应的DOM节点(也就是input节点)
e.target.value:input节点的value值,实际上就是输入框中的内容
问题:此时键入后只能在控制台上console.log看到e.target.value变化,页面输入框中毫无变化。
原因:虽然拿到了内容,但目前input组件的value是由state.inputValue决定的(并不是e.target.value控制显示),所以需要改变状态中的inputValue值
解决方法:需要将获取到的DOM节点的值赋值给state.inputValue,从而使得input展示出变化

思路1:尝试将e.target.value直接赋给inputValue,此时可发现this指向有问题
this.state.inputValue = e.target.value; // 报错,显示state未定义,即 this指向有问题
console.log(this); // 打印出的是undefined,说明handleInputChange函数内的this并未指向TodoList组件

思路2:handleInputChange使用bind(this)处理this指向,将函数内的this指向TodoList组件以便调用存放在它的状态中的inputValue
this.state.inputValue = e.target.value; // 还是报错,是因为react中改变状态中的数据需要调用组件的setState方法
*/
this.setState({
inputValue: e.target.value
})
}

}

export default TodoList;

代码分析

  • constructor(props){}函数是构造函数,在js里每个类都会有一个自带的构造函数(注意带一个参数props)
    • 当我们使用这个组件时,它会在render函数之前执行
    • 构造函数内必须有super(props); 去继承父类component的props
    • 构造函数内所有定义的数据都需要挂靠在当前对象的状态下,也就是写在this. state={}里面
    • 设计思路:将所有会导致页面变化的数据和state中的数据绑定在一起,后续直接操作state中的数据,接下来状态中的数据发生变化时会直接反应在页面上
  • 为了能在输入框中输入数据后显示在输入框下方,我们需要在构造函数的state中定义两个数据
    • inputValue用于存储输入框的值
    • list设为数组,用于存储显示在下方的列表
  • 注意:render函数中写的是JSX语法,constructor(props){}中写的是js代码
    • 如果想在JSX中插入js的变量或者表达式则需要使用{}将其框起
      • 如render函数中<input value={this.state.inputValue} />
  • 通过<input value={this.state.inputValue} />我们将状态中的inputValue赋给了输入框,此时输入框具有默认值,但如果不绑定onChange事件你不管输入什么框内都不会有所变化,因为通过我们对value的更改,后续都是从状态中读取inputValue再显示在页面上,而inputValue是没有变的。
  • 此时我们需要给输入框绑定事件onChange ,使输入框内容发生变化时触动函数handleInputChange ,通过函数handleInputChange将我们输入的值存储到状态中的inputValue上从而使页面发生变化
    • 注意:绑定事件处理函数的调用没有括号onChange={this.handleInputChange}),有括号就直接调用该处理函数了,没有括号则是指向处理函数地址,只有触动该事件时才调用函数
  • 绑定事件处理函数handleInputChange,事件处理函数默认接收一个参数也就是事件对象e,我们需要将state中的inputValue值赋值到value上,以此实现inputValue改变带动value改变带动input输入框内容变化的效果
    • 语法上,事件处理函数默认只传递一个参数,也就是事件对象
    • target对应的是事件对象e对应的DOM节点(也就是input节点)
    • input节点的value值实际上就是输入框中的内容
  • 尝试将e.target.value赋给inputValue:this.state.inputValue = e.target.value;可发现报错,显示state未定义,说明this指向有问题,打印出this发现是undefined,说明handleInputChange函数内的this并未指向TodoList组件
  • 尝试使用bind(this)将函数内的this指向TodoList组件以便调用存放在它的状态中的inputValue
    • 注意:在这里建议在构造函数中绑定bind(),比较节省性能,可以参考“bind()”笔记
  • 转换this指向以后还是报错,是因为react中改变状态中的数据需要调用组件的setState方法
    1
    2
    3
    this.setState({
    inputValue:e.target.value
    })

思路

  • 前提须知:input输入框显示内容由value控制,原生html中input中可以理解为value = e.target.vaue
    • e是事件对象
    • e.target是DOM元素,这里就是input元素
  • 我们希望数据改变视图,也就是通过数据变化影响视图变化,所以我们通过value = this.state.inputValue将value定为我们规定会引起视图变化的inputValue
  • 可是键盘输入字符修改input时,inputValue是不会自己变化的,会变化的是e.target.vaue,所以我们需要在键盘输入值(即 输入框内容变化)时,改变inputValue的值=>value属性值变化=>视图展示效果就会改变
    • 所以给input绑定change事件,事件函数handleInputChange内,通过setState将e.target.vaue赋值给inputValue

总结

  • 在js里每个类(组件)都会有一个自带的构造函数constructor(props){},(注意带一个参数props)
    • 当我们使用这个组件时,它会在render函数之前执行
    • 构造函数内必须有super(props); 去继承父类component的props
    • 构造函数内所有定义的数据都需要挂靠在当前对象的状态下,也就是写在this. state={}里面
  • render函数中写的是JSX语法,constructor(props){}中写的是js代码
    • 如果想在JSX中插入js的变量或者表达式则需要使用{}将其框起。(如render函数中<input value={this.state.inputValue} />
  • 原生js中内容变化时的事件绑定onchange,而react中是onChange在react中事件绑定时驼峰形式的!(如onClick,onChange等)
  • 事件绑定前要使用bind(this)进行函数作用域变更使函数中的this指向使其指向事件对象(即组件)才能调用组件的构造函数中的数据,否则函数中的this指向undefined
    • 注意:在这里建议在构造函数中绑定bind(),比较节省性能,可以参考“bind()”笔记
  • 修改this. state中的数据需要通过setState方法
    • 想要改变this. state里面的数据直接引用this. state进行赋值是不行的,要使用this.setState({state中需要修改的属性:修改以后的属性值;})(其实就是将需要修改的属性通过对象的方式传入setState函数)

实现 TodoList 新增及删除功能

新增功能

  • 之前我们实现的功能是:在输入框中输入文字时我们输入的内容可以同步显示在页面上(是的,虽然这个功能看起来完全不需要我们去实现)
  • 现在要完成的功能是:点击“提交”按钮之后,输入的内容会显示在下方的列表之中并清空输入框内容。
    新增功能效果图

思路

  • 注意:我们不关心DOM层面上的操作,我们只想让state中的数据发生变化页面就会发生变化,也就是只要点击按钮后将inputValue的值放到list数组中并清空inputValue即可。
  • 步骤:
    1. 点击“提交”按钮之后触发onClick事件绑定函数handleBtnClick,在函数中通过扩展运算符将原本的list与输入框中得到的inputValue合并数组后赋给list,然后清空输入框中的值。(到这我们虽然把输入框的值放入数组中,但数组并未设置在页面中显示)
    2. 需要通过数组的map方法将原本页面写死的ul列表改成数组list中遍历读出的内容 ,注意给数组中的每一项一个唯一的key属性值作为标识符。
  • 注意:JSX语法中写js表达式和js变量都需要{}括起来,表达式中的变量也需要再用{}括起来!!
    jsx语法示意图

补充:jsmap()遍历数组并进行操作

react中遍历数组时需要加key值

  • 在react中循环渲染(遍历数组)时需要给每一项增加一个唯一的key值作为标识符,否则虽不会报错但是会在控制台显示“Warning”:
    没有key值在控制台会报“警告”
  • 在这里我们暂时使用数组下标index作为key值,实际编程中这样的习惯是很不好的,但具体原因留到后面再写:
    使用数组下标index作为key值

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import React, { Component, Fragment } from 'react';/* 快捷键imrc */

class TodoList extends Component {
constructor(props) {/* 构造函数constructor带有参数props */
super(props);/* 继承父类Component的props */
this.state = {/* 所有的数据都需要保存在状态里(即this.state) */
/* inputValue用于存放输入框内容 */
inputValue: "",
/* list列表用于存放输入框下方数据 */
list: []
}
}

render() {
return (
<Fragment>
<div>
{/*
注意:如果不绑定onChange事件你不管输入什么框内都不会有所变化,
因为每次他都是从状态中读取inputValue再显示在页面上,而inputValue是没有变的
事件绑定函数handleInputChange被调用后,handleInputChange函数内的this指向会丢失(即undefined)
需要使用bind()绑定handleInputChange的this指向,使其指向TodoList组件的this
以便在handleInputChange函数中可以获取TodoList组件的inputValue
*/}
<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 }>{ item }</li>
})
}
</ul>
</Fragment>
)
}

/* 传入一个参数事件对象e(语法上,事件处理函数默认会传递一个参数,也就是事件对象) */
handleInputChange(e) {
/*
e.target:事件对象e对应的DOM节点(也就是input节点)
e.target.value:input节点的value值,实际上就是输入框中的内容
注意:此时value绑定的是state.inputValue,键入后只能改变e.target.value,而页面输入框中的显示由state.inputValue控制
所以需要将e.target.value赋值给inputValue,实现我们期望的动态效果
*/
this.setState({
inputValue: e.target.value
})
}

handleBtnClick() {
this.setState({
/* 通过扩展运算符将原本的list与输入框中得到的inputValue合并数组后赋给list */
list: [...this.state.list, this.state.inputValue],
/* 清空输入框中的值 */
inputValue: ""
})
}

}

export default TodoList;

删除功能

  • 实现功能:点击列表中的某一项时该项被删除。

复习:splice()删除数组部分的元素

  • splice()可用于删除、插入、替换数组中部分元素
    • 返回值是从原数组中删除的元素组成的数组
  • 复习删除用法
    • 注意:删除时,返回的是含有删除部分的元素的数组,原数组会变成被删除以后的数组。
  • 语法:arrayObject.splice(index, count)
    • 删除位置从index开始(包括index)
    • count是要删除的项目数量,如果设置为0,则不会删除项目
    • 如果不设置count,则删除从index开始的所有值
  • 返回值:含有被删除的元素的数组
  • 例子:
    1
    2
    3
    4
    5
    6
    7
    8
    var arr = ["a", "b", "c", "d", "e", "f"];
    //删除
    var delArr = arr.splice(5);//从数组下标为5的元素开始,包括5【注意是数组下标】
    console.log(arr);// ["a", "b", "c", "d", "e"]
    console.log(delArr);//["f"]
    var delArr = arr.splice(2, 2);
    console.log(arr);// ["a", "b", "e"]
    console.log(delArr);//["c", "d"]

思路

  1. 给每个li标签绑定一个 onClick 事件函数 handleItemDelete ,通过 bind 绑定 this 指向组件 TodoList 的同时将数组下标 index 通过第二个参数传到 handleItemDelete 中,以便我们在 handleItemDelete函数 中获取到数组下标
    (注意:在这里建议在构造函数中绑定bind(),比较节省性能,可以参考“bind()”笔记
    1
    2
    3
    4
    5
    6
    <li
    key = { index }
    onClick = { this.handleItemDelete.bind(this, index) }
    >
    { item }
    </li>
  2. 使用扩展运算符拷贝 state 中的 list 到 变量list1 中,对 变量list 使用 splice方法 删除选中数组下标为 index 的数组元素,再使用 setState方法 将删除被点击元素后的 变量list1 赋给 state 中的list
    1
    2
    3
    4
    5
    6
    7
    8
    handleItemDelete(index) {
    const list1 = [...this.state.list]
    list1.splice(index, 1);

    this.setState({
    list: list1
    })
    }
    注意:必须将state中的值拷贝出来再对拷贝出来的变量list1进行修改,不能直接修改state中的值,否则后期性能优化会很麻烦。(immutable:state 不允许我们做任何的改变)

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import React, { Component, Fragment } from 'react';/* 快捷键imrc */

class TodoList extends Component {
constructor(props) {/* 构造函数constructor带有参数props */
super(props);/* 继承父类Component的props */
this.state = {/* 所有的数据都需要保存在状态里(即this.state) */
/* inputValue用于存放输入框内容 */
inputValue: "",
/* list列表用于存放输入框下方数据 */
list: []
}
}

render() {
return (
<Fragment>
<div>
{/*
注意:如果不绑定onChange事件你不管输入什么框内都不会有所变化,
因为每次他都是从状态中读取inputValue再显示在页面上,而inputValue是没有变的
事件绑定函数handleInputChange被调用后,handleInputChange函数内的this指向会丢失(即undefined)
需要使用bind()绑定handleInputChange的this指向,使其指向TodoList组件的this
以便在handleInputChange函数中可以获取TodoList组件的inputValue
*/}
<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>
)
}

/* 传入一个参数事件对象e(语法上,事件处理函数默认只传递一个参数,也就是事件对象) */
handleInputChange(e) {
/* target对应的是事件对象e对应的DOM节点(也就是input节点),input节点的value值实际上就是输入框中的内容,但此时键入后只能在控制台上看到变化,页面输入框中毫无变化。这是因为虽然拿到了内容,但并未改变状态中的值,所以我们还需要修改的是状态中的inputValue,react中改变状态中的数据需要调用组件的setState方法 */
this.setState({
inputValue: e.target.value
})
}

handleBtnClick() {
this.setState({
/* 通过扩展运算符将原本的list与输入框中得到的inputValue合并数组后赋给list */
list: [...this.state.list, this.state.inputValue],
/* 清空输入框中的值 */
inputValue: ""
})
}

handleItemDelete(index) {
const list1 = [...this.state.list]
list1.splice(index, 1);

this.setState({
list: list1
})
}

}

export default TodoList;

总结注意事项

  1. 在JSX语法中插入js的变量或者js表达式则需要使用{}将其框起。表达式中的变量也需要再用{}括起来!
    jsx语法示意图
  2. 必须将state中的值拷贝出来再对拷贝出来的变量进行修改,不能直接修改state中的值,否则后期性能优化会很麻烦。

,