一、 基本概念:
1. 模块
1. 理解:向外提供特定功能的js程序, 一般就是一个js文件
2. 为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂。
3. 作用:复用js, 简化js的编写, 提高js运行效率
2. 组件
1. 理解:用来实现局部功能效果的代码和资源的集合(html/css/js/image等等)
2. 为什么要用组件: 一个界面的功能更复杂
3. 作用:复用编码, 简化项目编码, 提高运行效率
3. 模块化
当应用的js都以模块来编写的, 这个应用就是一个模块化的应用
4. 组件化
当应用是以多组件的方式实现, 这个应用就是一个组件化的应用
二、组件的使用
当应用是以多组件的方式实现,这个应用就是一个组件化的应用
注意:
- 组件名必须是首字母大写
- 虚拟DOM元素只能有一个根元素
- 虚拟DOM元素必须有结束标签
< />
渲染类组件标签的基本流程
- React 内部会创建组件实例对象
- 调用
render()
得到虚拟 DOM ,并解析为真实 DOM - 插入到指定的页面元素内部
1. 函数式组件(函数名就是组件名)
函数组件是使用function来进行定义的函数,只是这个函数会返回和类组件中render函数返回一样的内容。 函数组件有自己的特点(当然,后面我们会讲hooks,就不一样了):
- 没有生命周期,也会被更新并挂载,但是没有生命周期函数;
- 没有this(组件实例);
- 没有内部状态(state); 我们来定义一个函数组件:
//1.先创建函数,函数可以有参数,也可以没有,但是必须要有返回值 返回一个虚拟DOM
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
//2.进行渲染
ReactDOM.Render(<Welcom name = "ljc" />,document.getElementById("div"));
上面的代码经历了以下几步
- 我们调用
ReactDOM.render()
函数,并传入<Welcome name="ljc" />
作为参数。 - React 调用
Welcome
组件,并将{name: 'ljc'}
作为 props 传入。 Welcome
组件将Hello, ljc
元素作为返回值。- React DOM 将 DOM 高效地更新为
Hello,ljc
。
实例
<!-- 准备好一个“容器” -->
<div id="test"></div>
<script type="text/babel">
//1.创建函数式组件
function MyComponent() {
console.log('this:', this); //此处的this是undefined,
//因为babel编译后开启了严格模式, 严格模式禁止将自定义里的函数this指向window.
return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
}
//2.渲染组件到页面(组件名与函数名相对应,组件标签必须闭合且首字母必须大写)
ReactDOM.render(<MyComponent />, document.getElementById('test'))
执行了ReactDOM.render(.......)之后,发生了什么?
1.React解析组件标签,找到了MyComponent组件。
2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。
2. 类式组件(类名就是组件名)
类的基础知识
<script type="text/javascript">
/*
总结:
1.类中的构造器不是必须要写的,要对实例进行一些初始化的操作,如添加指定属性时才写。
2.如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super是必须要调用的。
3.类中所定义的方法,都放在了类的原型对象上,供实例去使用。
4.类中你定义的方法已经在局部开好了严格模式
*/
//创建一个Person类
class Person {
//构造器方法
constructor(name, age) {
//构造器中的this是谁?—— 类的实例对象
this.name = name
this.age = age
}
//一般方法
speak() {
//speak方法放在了哪里?—— 类的原型对象上,供实例使用
//通过Person实例调用speak时,speak中的this就是Person实例
console.log(`我叫${this.name},我年龄是${this.age}`);
}
}
const p1 = new Person('tom', 18)
const p2 = new Person('jack', 19)
console.log(p1);
console.log(p2);
p1.speak()
p2.speak()
//创建一个Student类,继承于Person类
class Student extends Person {
constructor(name, age, grade) {
super(name, age)
this.grade = grade
this.school = '尚硅谷'
}
//重写从父类继承过来的方法
speak() {
console.log(`我叫${this.name},我年龄是${this.age},我读的是${this.grade}年级`);
this.study()
}
study() {
//study方法放在了哪里?——类的原型对象上,供实例使用
//通过Student实例调用study时,study中的this就是Student实例
console.log('我很努力的学习');
}
}
const s1 = new Student('zgc', 21, '大四')
console.log(s1);
s1.speak()
s1.study()
class Car {
constructor(name, price) {
this.name = name
this.price = price
// this.wheel = 4
}
//类中可以直接写赋值语句,如下代码的含义是:给Car的实例对象添加一个属性,名为a,值为1
a = 1
wheel = 4
static demo = 100
}
const c1 = new Car('奔驰c63', 199)
console.log(c1);
console.log(Car.demo);//100
console.log(c1.demo);//undefined,static 定义静态方法和属性,声明的值只有自身才能够调用
</script>
类组件的定义有如下要求:
- 组件的名称是大写字符开头(无论类组件还是函数组件)
- 类组件需要继承自 React.Component
- 类组件必须实现render函数 使用class定义一个组件:
- constructor是可选的,我们通常在constructor中初始化一些数据;
- this.state中维护的就是我们组件内部的数据;
- render() 方法是 class 组件中唯一必须实现的方法 当 render 被调用时,它会检查 this.props 和 this.state 的变化并返回以下类型之一:
- React 元素( 通常通过 JSX 创建,如
<div/>
会被 React 渲染为 DOM 节点,MyComponent
会被 React 渲染为自定义组件;无论是<div/>
还是MyComponent
均为 React 元素。) - 数组或 fragments(片段):使得 render 方法可以返回多个元素。
- Portals:可以渲染子节点到不同的 DOM 子树中。
- 字符串或数值类型:它们在 DOM 中会被渲染为文本节点
- 布尔类型或 null:什么都不渲染。
实例
<!-- 准备好一个“容器” -->
<div id="test"></div>
<script type="text/babel">
//1.创建类式组件
class MyComponent extends React.Component {
render(){
//render是放在哪里的?—— MyComponent的原型对象上,供实例使用。
//render中的this是谁?—— MyComponent的实例对象 <=> MyComponent组件实例对象
console.log('render中的this:',this);
return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
}
}
//2.渲染组件到页面
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
</script>
执行了ReactDOM.render(.......之后,发生了什么?
1.React解析组件标签,找到了MyComponent组件
2.发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用原型上的render方法
3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面中
三、组件实例的三大属性--用class创建组件实例
1. state
React 把组件看成是一个状态机(State Machines)。通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致。
React 里,只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。
简单的说就是组件的状态,也就是该组件所存储的数据
类式组件中的使用
<div id="test"></div>
<script type="text/babel">
//1.创建组件
class Weather extends React.Component {
//构造器调用几次? ———— 1次
constructor(props) {
//构造器中的this固定为组件实例对象
super(props)
//初始化状态
this.state = { isHot: false, wind: '微风' }
//this实例沿着原型链找到changeWeather,用bind将原型上的changeWeather
//(即下面写的changeWeather函数)改变了this指向,赋给组件实例自身
//(会有两个changeWeather,一个在原型,一个在自身)
this.changeWeather = this.changeWeather.bind(this)
}
//render调用几次? ———— 1+n次 1是初始化的那次 n是状态更新的次数
render() {
//render中的this也是组件实例对象,因为 ReactDOM.render new出来该类的实例,并通过该实例调用原型上的render方法
//解构赋值读取状态
const { isHot, wind } = this.state
//绑定事件用驼峰命名,{}中的自定义函数不要加()
//这时候就会首先调用实例自身的changeWeather
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
}
//changeWeather调用几次? ———— 点几次调几次
changeWeather() {
//changeWeather放在哪里? ———— Weather的原型对象上,供实例使用
//由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
//类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined
console.log(this);
//获取原来的isHot值
const isHot = this.state.isHot
//严重注意:状态(state)必须通过setState进行更新,且更新是一种合并,不是替换,state中其他属性不会被更新。
this.setState({ isHot: !isHot })
//严重注意:状态(state)不可直接更改,下面这行就是直接更改!!!
//this.state.isHot = !isHot //这是错误的写法
}
}
//2.渲染组件到页面
ReactDOM.render(<Weather />, document.getElementById('test'))
</script>
- changeWeather 作为 onClick 的回调,是用this从实例对象直接的原型链中找到
changeWeather
,然后再赋值给onClick,点击之后直接从堆中将函数调用,根本不是从实例对象中调用。类中的方法默认开启了局部的严格模式,此时的this是undefined。 - bind: 做了两件事情 ---- 生成新的函数并且改变其this为Weather的实例对象,this.changeWeather是原型上的方法,通过bind改变this之后生成新的方法放在了实例自身上,导致了实例中也有changeWeather这个方法,这样就能进行调用了。
思考: 那为什么 render
函数里面的 this
指向的不是 undefined?
解释:JSX代码经过 Babel 编译之后变成 React 的表达式,这个表达式在render函数被调用的时候通过 React 的
createElement
方法生成一个element,在这个方法中,this指向了被创建的类(也就是相应的React组件)。
简写
<!-- 准备好一个“容器” -->
<div id="test"></div>
<script type="text/babel">
//1.创建组件
class Weather extends React.Component {
//初始化状态,类中可以直接写赋值语句,如下代码的含义是:给Weather的实例对象添加一个属性state
state = { isHot: false, wind: '微风' }
render() {
const { isHot, wind } = this.state
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
}
//自定义方法————要用赋值语句的形式+箭头函数の形式
// 这样就不需要在constructor中改变this指向了,这时changeWeather在组件实例自身,而不是再其原型链中
changeWeather = () => {
const isHot = this.state.isHot
this.setState({ isHot: !isHot })
}
}
//2.渲染组件到页面
ReactDOM.render(<Weather />, document.getElementById('test'))
</script>
在优化过程中遇到的问题
-
组件中的 render 方法中的 this 为组件实例对象
-
组件自定义方法中由于开启了严格模式,this 指向
undefined
如何解决-
通过 bind 改变 this 指向
-
推荐采用箭头函数+赋值语句的形式,这样就不需要在constructor中改变this指向,
-
state 数据不能直接修改或者更新,需要通过
setState()
修改
-
这玩意底层不简单,this
的指向真的需要好好学习
2. props
与state
不同,state
是组件自身的状态,而props
则是外部传入的数据
- 每个组件对象都会有props(properties的简写)属性
- 组件标签的所有属性都保存在props中
- 通过标签属性从组件外向组件内传递变化的数据
- 注意: 组件内部不要修改props数据
类式组件中使用
<script type="text/babel">
//创建组件
class Person extends React.Component {
render() {
// 解构赋值,从props取出值,简化写法
const { name, age, sex } = this.props
return (
<ul>
<li>姓名:{this.props.name}</li>
<li>性别:{sex}</li>
<li>年龄:{age + 1}</li>
</ul>
)
}
}
//渲染组件到页面
ReactDOM.render(<Person name="jerry" age={19} sex="男" />, document.getElementById('test1'))
//组件标签中引号不能去掉,会报错,想将其表示为数字则用{}
ReactDOM.render(<Person name="tom" age='18' sex="女" />, document.getElementById('test2'))
const p = { name: '老刘', age: 18, sex: '女' }
// ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>,document.getElementById('test3'))
// 简化批量接收的数据
ReactDOM.render(<Person {...p} />, document.getElementById('test3'))
</script>
使用 PropTypes 进行类型检查
<!-- 引入prop-types,用于对组件标签属性进行限制 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
<script type="text/babel">
//创建组件
class Person extends React.Component {
render() {
const { name, age, sex } = this.props
//props是只读的
//this.props.name = 'jack' //此行代码会报错,因为props是只读的
return (
<ul>
<li>姓名:{this.props.name}</li>
<li>性别:{sex}</li>
<li>年龄:{age + 1}</li>
</ul>
)
}
}
//对标签属性进行类型、必要性的限制
Person.propTypes = {
name: PropTypes.string.isRequired, //限制name必传,且为字符串
sex: PropTypes.string,//限制sex为字符串
age: PropTypes.number,//限制age为数值
speak: PropTypes.func,//限制speak为函数
}
//指定默认标签属性值
Person.defaultProps = {
sex: '男',//sex默认值为男
age: 18 //age默认值为18
}
//渲染组件到页面
ReactDOM.render(<Person name='jack' speak={speak} />, document.getElementById('test1'))
ReactDOM.render(<Person name="tom" age={18} sex="女" />, document.getElementById('test2'))
const p = { name: '老刘', age: 18, sex: '女' }
// ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>,document.getElementById('test3'))
ReactDOM.render(<Person {...p} />, document.getElementById('test3'))
function speak() {
console.log('我说话了');
}
</script>
简写(将对props的规范和默认值写在类式组件之内)
<!-- 引入prop-types,用于对组件标签属性进行限制 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
<script type="text/babel">
//创建组件
class Person extends React.Component{
constructor(props){
//构造器是否接收props,是否传递给super,取决于是否希望在构造器中通过this访问props
// console.log(props);
super(props)
console.log('constructor',this.props);
}
//对标签属性进行类型、必要性的限制
static propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string,//限制sex为字符串
age:PropTypes.number,//限制age为数值
}
//指定默认标签属性值
static defaultProps = {
sex:'男',//sex默认值为男
age:18 //age默认值为18
}
render(){
// console.log(this);
const {name,age,sex} = this.props
//props是只读的
//this.props.name = 'jack' //此行代码会报错,因为props是只读的
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age+1}</li>
</ul>
)
}
}
//渲染组件到页面
ReactDOM.render(<Person name="jerry"/>,document.getElementById('test1'))
</script>
在使用的时候可以通过 this.props
来获取值 类式组件的 props
:
- 通过在组件标签上传递值,在组件中就可以获取到所传递的值
- 在构造器里的
props
参数里可以获取到props
- 可以分别设置
propTypes
和defaultProps
两个属性来分别操作props
的规范和默认值,两者可以直接添加在类式组件的原型对象上(所以需要添加static
),也可以写在组件外侧,需要用script标签引入一个第三方库,或者yarn add prop-types
- 同时可以通过
...
运算符来简化
函数式组件中的使用
函数在使用props的时候,是作为参数进行使用的(props)
函数组件的 props
定义:
- 在组件标签中传递
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>姓名:{props.name}</li>
<li>性别:{props.sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
//对标签属性进行类型,必要性的限制
Person.propTypes = {
name: PropTypes.string.isRequired, //限制name必传,且为字符串
sex: PropTypes.string,//限制sex为字符串
age: PropTypes.number,//限制age为数值
}
//指定默认标签属性值
Person.defaultProps = {
sex: '男',//sex默认值为男
age: 18 //age默认值为18
}
//渲染组件到页面
ReactDOM.render(<Person name="jack"/>, document.getElementById('test1'))
</script>
子组件给父组件传值
某些情况,我们也需要子组件向父组件传递消息:
- 在vue中是通过自定义事件来完成的;
- 在React中同样是通过props传递消息,只是让父组件给子组件传递一个回调函数,在子组件中调用这个函数即可; 我们这里来完成一个案例:
将计数器案例进行拆解;将按钮封装到子组件中:CounterButton; CounterButton发生点击事件,将内容传递到父组件中,修改counter的值
import React, { Component } from "react";
// 子
class CounterButton extends Component {
render() {
const { onClick } = this.props;
return <button onClick={() => onClick(1)}>+1</button>;
}
}
// 父
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
counter: 0,
};
}
render() {
return (
<div>
<h2>当前计数: {this.state.counter}</h2>
<CounterButton onClick={this.increment} name="why" />
</div>
);
}
increment = (number) => {
this.setState({
counter: this.state.counter + number,
});
};
}
案例--页面切换显示不同内容:
//父:App.jsx
import React, { Component } from "react";
import TabControl from "./TabControl";
export default class App extends Component {
state = {
currentTitle: "新款",
titles: ["新款", "精选", "流行"],
};
render() {
const { currentTitle, titles } = this.state;
return (
<div>
<TabControl
titles={titles}
itemClickTitle={(index) => this.itemClickTitle(index)}
/>
<h2 className="zgc">{currentTitle}</h2>
</div>
);
}
itemClickTitle(index) {
this.setState({
currentTitle: this.state.titles[index],
});
}
}
//子:TabControl.jsx
import React, { Component } from "react";
import PropTypes from "prop-types";
export default class TabControl extends Component {
state = {
currentIndex: 0,
};
render() {
const { titles } = this.props;
const { currentIndex } = this.state;
return (
<div className="tab-control">
{titles.map((item, index) => {
return (
<div
key={item}
className={"tab-item " + (index === currentIndex ? "active" : "")}
onClick={() => this.itemClick(index)}
>
<span>{item}</span>
</div>
);
})}
</div>
);
}
itemClick(index) {
this.setState({
currentIndex: index,
});
this.props.itemClickTitle(index);
}
}
TabControl.propTypes = {
titles: PropTypes.array.isRequired,
};
//样式:style.css
.tab-control {
display: flex;
height: 44px;
line-height: 44px;
}
.tab-item {
flex: 1;
text-align: center;
}
.tab-item span {
padding: 5px 8px;
}
.tab-item.active {
color: red;
}
.tab-item.active span {
border-bottom: 3px solid red;
}
.zgc{
text-align: center;
font-size: 80px;
font-family: '方正舒体';
}
3. refs
Refs 提供了一种方式,允许我们访问 DOM 节点或在 render
方法中创建的 React 元素。
在我们正常的操作节点时,需要采用DOM API 来查找元素,但是这样违背了 React 的理念,因此有了
refs
有三种操作refs
的方法,分别为:
- 字符串形式
- 回调形式
createRef
形式
字符串形式refs
将DOM节点存储在refs中,引用this.refs.input1
<!-- 准备好一个“容器” -->
<div id="test"></div>
<script type="text/babel">
//创建组件
class Demo extends React.Component {
//展示左侧输入框的数据
showData = () => {
const input1 = this.refs.input1
alert(input1.value)
console.log(this);
}
//展示右侧输入框的数据
showData2 = () => {
const { input2 } = this.refs
alert(input2.value)
}
render() {
return (
<div>
<input ref="input1" type="text" placeholder="点击按钮提示数据" />
<button onClick={this.showData}>点我提示左侧的数据</button>
<input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo a="1" b="2" />, document.getElementById('test'))
</script>
虽然这个方法不推荐,但是还能用,还很好用hhh~它已过时并可能会在未来的版本被移除。
回调形式的refs
组件实例的ref
属性传递一个回调函数c => this.input1 = c
(箭头函数简写),这样会在实例的属性中存储对DOM节点的引用,使用时可通过this.input1
来使用
使用方法
<!-- 准备好一个“容器” -->
<div id="test"></div>
<script type="text/babel">
//创建组件
class Demo extends React.Component {
//展示左侧输入框的数据
showData = () => {
const input1 = this.input1
alert(input1.value)
console.log(this);
}
//展示右侧输入框的数据
showData2 = () => {
//解构赋值
const { input2 } = this
alert(input2.value)
console.log(this);
}
render() {
return (
<div>
{/*回调形式的ref,传入的参数为ref绑定的DOM元素,最后将DOM元素赋给组件自身的属性input1*/}
<input ref={(c) => { this.input1 = c }} type="text" placeholder="点击按钮提示数据" />
<button onClick={this.showData}>点我提示左侧的数据</button>
<input onBlur={this.showData2} ref={c => this.input2 = c} type="text" placeholder="失去焦点提示数据" />
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo a="1" b="2" />, document.getElementById('test'))
</script>
我的理解
c
会接收到当前节点作为参数,ref
的值为函数的返回值,也就是this.input1 = c
,因此是给实例下的input1
赋值
如果
ref
回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数null
,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。 class绑定函数的写法
<!-- 准备好一个“容器” -->
<div id="test"></div>
<script type="text/babel">
//创建组件
class Demo extends React.Component{
state = {isHot:false}
showInfo = ()=>{
const {input1} = this
alert(input1.value)
}
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={(c)=>{this.input1 = c;console.log('@',c);}} type="text"/><br/><br/>*/}
<input ref={this.saveInput} type="text"/><br/><br/>
<button onClick={this.showInfo}>点我提示输入的数据</button>
<button onClick={this.changeWeather}>点我切换天气</button>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo/>,document.getElementById('test'))
</script>
createRef 形式(推荐写法)
React 给我们提供了一个相应的API,它会自动的将该 DOM 元素放入实例对象中
<!-- 准备好一个“容器” -->
<div id="test"></div>
<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="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo a="1" b="2"/>,document.getElementById('test'))
</script>
注意:我们不要过度的使用 ref,如果发生时间的元素刚好是需要操作的元素,就可以使用事件对象去替代。过度使用有什么问题我也不清楚,可能有 bug 吧
案例
通过ref在父组件中直接调用子组件的方法
import React, { PureComponent, createRef } from "react";
class Counter extends PureComponent {
state = {
counter: 0,
};
render() {
return (
<div>
<h2>当前计数: {this.state.counter}</h2>
<button onClick={(e) => this.increment()}>+1</button>
</div>
);
}
increment() {
this.setState({
counter: this.state.counter + 1,
});
}
}
export default class App extends PureComponent {
counterRef = createRef();
render() {
return (
<div>
<Counter ref={this.counterRef} />
<button onClick={() => this.appBtnClick()}>App按钮</button>
</div>
);
}
appBtnClick() {
this.counterRef.current.increment();
}
}
4. 事件处理
(1).通过onXxx属性指定事件处理函数(注意大小写)
a.React使用的是自定义(合成)事件, 而不是使用的原生DOM事件 —为了更好的兼容性
b.React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) —为了的高效
(2).通过event.target得到发生事件的DOM元素对象 ——不要过度使用ref
<!-- 准备好一个“容器” -->
<div id="test"></div>
<script type="text/babel">
//创建组件
class Demo extends React.Component{
//创建ref容器
myRef = React.createRef()
myRef2 = React.createRef()
//展示左侧输入框的数据
showData = (event)=>{
// console.log(event.target);
// 此处不能用event,因为触发事件的元素为button
alert(this.myRef.current.value);
}
//展示右侧输入框的数据
showData2 = (event)=>{
alert(event.target.value);
}
//发生事件的元素正好是你要操作的元素,可以省略ref
render(){
return(
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo a="1" b="2"/>,document.getElementById('test'))
</script>
5.非受控组件与受控组件
受控组件
在 HTML 中,表单元素(如<input>
、 <textarea>
和 <select>
)通常自己维护 state,并根据用户输入进行更新。而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()
来更新。
我们可以把两者结合起来,使 React 的 state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做受控组件。
案例1:受控组件的基本使用
import React, { PureComponent } from "react";
export default class App extends PureComponent {
state = {
username: "",
};
render() {
return (
<div>
<form onSubmit={(e) => this.handleSubmit(e)}>
<label htmlFor="username">
用户:
{/* 受控组件 */}
<input
type="text"
id="username"
onChange={(e) => this.handleChange(e)}
value={this.state.username}
/>
</label>
<input type="submit" value="提交" />
</form>
</div>
);
}
handleSubmit(event) {
event.preventDefault();
console.log(this.state.username);
}
handleChange(event) {
this.setState({
username: event.target.value,
});
}
}
案例2:受控组件的select使用
import React, { PureComponent } from 'react'
export default class App extends PureComponent {
constructor(props) {
super(props);
this.state = {
fruits: "orange"
}
}
render() {
return (
<div>
<form onSubmit={e => this.handleSubmit(e)}>
<select name="fruits"
onChange={e => this.handleChange(e)}
value={this.state.fruits}>
<option value="apple">苹果</option>
<option value="banana">香蕉</option>
<option value="orange">橘子</option>
</select>
<input type="submit" value="提交"/>
</form>
</div>
)
}
handleSubmit(event) {
event.preventDefault();
console.log(this.state.fruits);
}
handleChange(event) {
this.setState({
fruits: event.target.value
})
}
}
案例3:受控组件多输入简写
import React, { PureComponent } from "react";
export default class App extends PureComponent {
state = {
username: "",
password: "",
valid: "",
};
render() {
return (
<div>
<form onSubmit={(e) => this.handleSubmit(e)}>
<label htmlFor="username">
用户:
<input
type="text"
id="username"
name="username"
onChange={(e) => this.handleChange(e)}
value={this.state.username}
/>
</label>
<br />
<label htmlFor="password">
密码:
<input
type="text"
id="password"
name="password"
onChange={(e) => this.handleChange(e)}
value={this.state.password}
/>
</label>
<br />
<label htmlFor="valid">
验证:
<input
type="text"
id="valid"
name="valid"
onChange={(e) => this.handleChange(e)}
value={this.state.valid}
/>
</label>
<br />
<input type="submit" value="提交" />
</form>
</div>
);
}
handleSubmit(event) {
event.preventDefault();
const { username, password, valid } = this.state;
console.log(username, password, valid);
}
handleChange(event) {
// console.log(event.target.name);
this.setState({
// 计算属性名
[event.target.name]: event.target.value,
});
}
// 计算属性名 可以声明 一个变量 然后给对象当做属性使用
// var name= 'nickName'
// var obj = {
// [name]:'王麻子'
// }
// handleUsernameChange(event) {
// this.setState({
// username: event.target.value
// })
// }
// handlePasswordChange(event) {
// this.setState({
// password: event.target.value
// })
// }
// handleValidChange(event) {
// this.setState({
// valid: event.target.value
// })
// }
}
非受控组件
在大多数情况下,我们推荐使用 受控组件 来处理表单数据。在一个受控组件中,表单数据是由 React 组件来管理的。另一种替代方案是使用非受控组件,这时表单数据将交由 DOM 节点来处理。
要编写一个非受控组件,而不是为每个状态更新都编写数据处理函数,你可以 使用 ref 来从 DOM 节点中获取表单数据。
import React, { PureComponent, createRef } from "react";
export default class App extends PureComponent {
usernameRef = createRef();
render() {
return (
<div>
<form onSubmit={(e) => this.handleSubmit(e)}>
<label htmlFor="username">
用户:
<input type="text" id="username" ref={this.usernameRef} />
</label>
<input type="submit" value="提交" />
</form>
</div>
);
}
handleSubmit(event) {
event.preventDefault();
console.log(this.usernameRef.current.value);
}
}
四、 购物车案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
table {
border: 1px solid #eee;
border-collapse: collapse;
}
th,
td {
border: 1px solid #eee;
padding: 10px 16px;
text-align: center;
}
th {
background-color: #ccc;
}
.count {
margin: 0 5px;
}
</style>
</head>
<body>
<div id="app"></div>
<script src="../react/react.development.js"></script>
<script src="../react/react-dom.development.js"></script>
<script src="../react/babel.min.js"></script>
<script type="text/babel">
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
books: [
{
id: 1,
name: "《算法导论》",
date: "2006-9",
price: 85.0,
count: 2,
},
{
id: 2,
name: "《UNIX编程艺术》",
date: "2006-2",
price: 59.0,
count: 1,
},
{
id: 3,
name: "《编程珠玑》",
date: "2008-10",
price: 39.0,
count: 1,
},
{
id: 4,
name: "《代码大全》",
date: "2006-3",
price: 128.0,
count: 1,
},
],
};
}
// 格式化数据
formatPrice(price) {
if (typeof price !== "number") {
price = Number(price) || 0;
}
return "¥" + price.toFixed(2);
}
//
changeBookCount(index, count) {
// React中设计原则: state中的数据的不可变性;所以不能直接修改原数组
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 this.formatPrice(totalPrice);
}
// 表格显示格式函数
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 key={index + 1}>
<td>{index + 1}</td>
<td>{item.name}</td>
<td>{item.date}</td>
<td>{this.formatPrice(item.price)}</td>
<td>
<button
{/*书籍数量小于等于1减号按钮不能在交互了*/}
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();
}
}
ReactDOM.render(<App />, document.getElementById("app"));
</script>
</body>
</html>