事件处理
react 内置组件的事件处理
react 内置组件是指 react 中已经定义好的,可以直接使用的如 div、button、input 等与原生 HTML 标签对应的组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<a href="http://www.baidu.com" id="dom">百度一下</a>
<script type="text/javascript">
document.getElementById('dom').addEventListener('click', event => {
event.preventDefault(); // 阻止默认行为
})
</script>
</body>
</html>
以上是 javascript 模式下阻止超链接默认行为的方式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.bootcdn.net/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/babel-standalone/7.0.0-beta.3/babel.min.js"></script>
</head>
<body>
<div id="main"></div>
<script type="text/babel">
class App extends React.Component {
constructor() { super(); }
render() {
const handleSubmit = event => {
event.preventDefault(); // 阻止默认行为
};
return (<a href="http://www.baidu.com" onClick={handleSubmit}>百度一下</a>)
}
}
ReactDOM.render( <App />, document.getElementById('main') );
</script>
</body>
</html>
react 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同:
- react 事件的命名采用小驼峰式(camelCase),而不是纯小写
- 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串
const handleSubmit = event => {
event.preventDefault(); // 阻止默认行为
console.log(event);
console.log(event.target); // 拿到一个 DOM
console.log(event.target.innerHTML); // 获取到 DOM 的 innerHTML 值
}
可以通过 event
对象映射的 target
属性,间接拿到这个元素的 DOM 对象,通过 DOM 的 Properties
属性执行原生 DOM 操作,通常在做一个输入文本框全局搜索功能业务的时候,通过 event.target
拿到搜索信息并取值,就可以少声明一个组件状态进行维护
render() {
const handleSubmit = event => {
if(event.keyCode == 13) {
console.log(event.target.value);
}
}
return (<input onKeyDown={handleSubmit} />)
}
对比原生 DOM 事件对象,通常针对不同浏览器进行兼容处理,而 react 中封装的一套事件对象是屏蔽了不同浏览器之间的使用差异
react 事件对象也叫 SyntheticEvent
(合成对象),每个事件对象都会接受到一个 event
对象作为参数,这也方便了在 react 进行 javascript 原生 DOM 操作支持
const handleSubmit = event => {
event.preventDefault(); // 阻止默认行为
setTimeout(function() {
console.log(event.target); // null
console.log(event.target.innerHTML); // Cannot read properties of null (reading 'innerHTML')
}, 2000)
}
react 对 DOM 的渲染是基于服务器渲染机制进行高效率的执行渲染,处于性能的考虑,react 并不是为每一个事件处理函数生成一个全新的事件对象,事件对象会被复用,当事件处理函数被执行以后,事件对象的所有属性会被设置为 null
不要在 react 组件中使用 addEventListener
react 内部自己实现了一套高效的事件机制,为了提高框架的性能,react 通过 DOM 事件冒泡,只在 document
节点上注册原生的 DOM 事件,react 内部自己管理所有组件的事件处理函数,以及事件的冒泡、捕获
componentDidMount() {
document.querySelector('.dom').addEventListener('click', event => {
console.log(event.target.innerHTML);
});
}
render() {
const handButn = event => {
event.stopPropagation(); // 没法阻止冒泡事件
console.log(event.target.innerHTML);
}
return (
<div className='dom'>
<button onClick={handButn}>确定</button>
</div>
)
}
条件渲染
react 中的条件渲染和 javascript 中的一样,使用 javascript 运算符 if
或者 ?:
三目运算符去创建元素来表现当前的状态,然后让 react 根据它们来更新 UI
render() {
const LoginButton = (<span>登录</span>); // 声明登陆组件
const LogonButton = (<span>登出</span>); // 声明登出组件
const state = true;
return (
<div className='user-logn'>
{ state ? <LoginButton /> : <LogonButton /> }
</div>
)
}
用户只需要操作改变 state
这个状态,就能够得到不同的组件渲染结果,不能如下方式直接引用 if-else
条件语句,因为 react 中的花括号 ”{}“ 内一定为结果,是用于服务器渲染的,可以是任意数据类型值,也可以是组件,不能直接使用 javascript 表达式语句
render() {
const LoginButton = (<span>登录</span>); // 声明登陆组件
const LogonButton = (<span>登出</span>); // 声明登出组件
const state = true;
return (
<div className='user-logn'>
{
if (state) {
return <LoginButton />
} else {
return <LogonButton />
}
}
</div>
)
}
需要注意的是 javascript 原生表达式一定要放在服务器渲染外面
render() {
const LoginButton = (<span>登录</span>); // 声明登陆组件
const LogonButton = (<span>登出</span>); // 声明登出组件
let state = true, userLogn = null;
if (state) {
userLogn = <LoginButton />;
} else {
userLogn = <LogonButton />;
}
return (
<div className='user-logn'>
{ userLogn }
</div>
)
}
react 条件渲染,不仅仅在于条件语句中,通过数组与索引关系、对象与 Key 值关系,同样能达到条件判定效果
render() {
const stateList = [<span>登录失败</span>, <span>登录成功</span>];
const index = 0;
return (
<div className='user-logn'>
{ stateList[index] }
</div>
)
}
render() {
const stateObject = { success: <span>登录成功</span>, fail: <span>登录成功</span> };
const result = 'success';
return (
<div className='user-logn'>
{ stateList[result] }
</div>
)
}
像状态精确到某个值的时候,如果使用多重 ?:
条件语句,会导致代码的可读性不高,推荐使用上述两种对象取值模式,在使用 ?:
条件语句时,尽量不要超过两重嵌套,推荐使用一次
状态是一个区间值时,且存在多重的情况,那就把条件抽离到服务器渲染外面判定,采用 if-else
方式,通过 result
结果对象来赋值条件判定结果
列表渲染
render() {
const list = ['aaaaaa', 'bbbbbb', 'cccccc', 'dddddd'];
return (
<div className='list-view'>
{
list.length > 0 ? (
<ul className='list-main'>
{
list.map((v, k) => (<li key={k}>{v}</li>))
}
</ul>
) : null
}
</div>
)
}
react 中对列表数据的渲染需要注意几点:
- 在渲染列表数据之前,一定要对列表进行判定,只有列表存在的时候才进行渲染
- 列表数据渲染通常使用数组的
map
方法 - 列表数据渲染的每列都必须绑定一个
key
值,key
用于帮助 react 识别元素变化赋予的唯一标识
虚拟 DOM
虚拟 DOM 可以看做一棵模拟了 DOM 树的 javascript 对象树
原生 javascript 对数据列表的渲染,数据发生改变后,需要重新渲染列表
var domUl = document.querySeletor('.list-main');
list.map(va => { var domLi = document.createElement('li'); domLi.innerHTML = va; domUl.appendChild(domLi) })
list[0] = 'eeeeee'; domUl.innerHTML = '';
list.map(va => { var domLi = document.createElement('li'); domLi.innerHTML = va; domUl.appendChild(domLi) })
--------------------------------------------- ---------------------------------------------
| content | location | document | | content | location | document |
--------------------------------------------- ---------------------------------------------
| aaaaaa | dom01 | <li>aaaaaa</li> | | aaaaaa | dom05 | <li>eeeeee</li> |
| bbbbbb | dom02 | <li>bbbbbb</li> | | bbbbbb | dom06 | <li>bbbbbb</li> |
| cccccc | dom03 | <li>cccccc</li> | | cccccc | dom07 | <li>cccccc</li> |
| dddddd | dom04 | <li>dddddd</li> | | dddddd | dom08 | <li>dddddd</li> |
--------------------------------------------- ---------------------------------------------
react 对数据列表的渲染,数据发生改变后,仅仅将发生改变的数据映射修改,每个 key
指向一个映射堆栈 location
(地址)
--------------------------------------------- ---------------------------------------------
| content | location | document | | content | location | document |
--------------------------------------------- ---------------------------------------------
| aaaaaa | dom01 | <li>aaaaaa</li> | | aaaaaa | dom01 | <li>eeeeee</li> |
| bbbbbb | dom02 | <li>bbbbbb</li> | | bbbbbb | dom02 | <li>bbbbbb</li> |
| cccccc | dom03 | <li>cccccc</li> | | cccccc | dom03 | <li>cccccc</li> |
| dddddd | dom04 | <li>dddddd</li> | | dddddd | dom04 | <li>dddddd</li> |
--------------------------------------------- ---------------------------------------------
状态提升
react 是一种单向性数据流进行组件之间的传值的,但对于代数多的组件传值,就显得非常麻烦.
兄弟组件之间的状态进行传递,通常采用的方式将两者的状态放到父组件内,这种跨度传值,不符合 react 自上向下传递状态的范式,这里就需要对状态进行管理设计,Context 是 react 提供的一种在组件之间共享值的方式,而不必显式地通过组件树的逐层传递 props
const { Provider, Consumer } = React.createContext();
function ToolHead() {
return (
<Consumer>
{ (name) => <span>{name}</span> }
</Consumer>
)
}
function ToolFoot() {
return (
<Consumer>
{ (name) => <span>{name}</span> }
</Consumer>
)
}
function Container() { // 定义了一个容器组件
return (
<div>
<ToolHead />
<ToolFoot />
</div>
)
}
class APP extends React.Component {
render() {
let name = '小人头';
return (<Provider value={name}><Container /></Provider>)
}
}
通过 ToolHead
和 ToolFoot
可以看出,使用 App
组件的状态,无需通过 props
传递获取,同理,不管是第 N 代都可以使用这种方式
不推荐使用这种方式引用,每次构造起来较为复杂,后续采用 redux
来进行状态管理结合 props
来开发实际业务
组件形态
基于组件声明的方式及其应用场景,分为如下五种组件:
- 傀儡组件:纯静态的将接收数据展示到界面上;
- 状态组件:处理不同时刻下更新状态,带有生命周期;
- 容器组件:用户获取数据和业务逻辑处理;
- 高阶组件:会返回组件的组件,类似插件,处理校验、获取数据;
- 渲染回调:将渲染逻辑委托给其子组件【props.children】,用于控制渲染机制;
什么是高阶函数
高阶函数是对其他函数进行操作的函数,操作可以是将它们作为参数,或者是返回它们,是一个接收函数作为参数或将函数作为输出返回的函数
在 javascript 原型链中,就接触到了很多内置的高阶函数,例:Array.prototype.map
var arr = [1, 2, 3, 4, 5], sum = 0;
// 不使用高阶函数,通过循环迭代进行数组数据的叠加
for(var i = 0; i < arr.length; i++) { sum += i; }
// 使用高阶函数,可以非常便捷的得到数组数据的叠加
arr.map(v => sum+= v);
通过原型链来定义一个高阶函数:
Date.prototype.format = function(fmt) {
var o = {
"M+" : this.getMonth()+1, //月份
"d+" : this.getDate(), //日
"h+" : this.getHours(), //小时
"m+" : this.getMinutes(), //分
"s+" : this.getSeconds(), //秒
"q+" : Math.floor((this.getMonth()+3)/3), //季度
"S" : this.getMilliseconds() //毫秒
};
if(/(Y+)/.test(fmt)) {
fmt=fmt.replace(RegExp.$1, (this.getFullYear()+"").substr(4 - RegExp.$1.length));
}
for(var k in o) {
if(new RegExp("("+ k +")").test(fmt)){
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length==1) ? (o[k]) : (("00"+ o[k]).substr((""+ o[k]).length)));
}
}
return fmt;
}
什么是高阶组件
高阶组件是 react 应用中很重要的一部分,最大的特点就是重用组件逻辑。它并不是由 react API 定义出来的功能,而是由 react 的组合特性衍生出来的一种设计模式
const { Provider, Consumer } = React.createContext();
const sHoc = Temp => {
return (
<Consumer>
{ (name) => <Temp name={name} /> }
</Consumer>
)
}
const ToolHead = (props) => {
return (<span>{props.name}</span>)
}
const ToolFoot = (props) => {
return (<span>{props.name}</span>)
}
function Container() {
return (
<div>
{ sHoc(ToolHead) }
{ sHoc(ToolFoot) }
</div>
)
}
class APP extends React.Component {
render() {
let name = '小人头';
return (<Provider value={name}><Container /></Provider>)
}
}
如上:sHoc
就是一个高阶组件,让后续使用 node 环境创建的 react 项目,可以将高阶函数暴露出来,如下:
import React, { Component } from 'react';
const { Consumer } = React.createContext();
const sHoc = Temp => {
return (
<Consumer>
{ (name) => <Temp name={name} /> }
</Consumer>
)
}
export default sHoc;
import React from 'react';
import sHoc from './sHoc';
const ToolHead = (props) => {
return (<span>{props.name}</span>)
}
export default sHoc(ToolHead);
import React from 'react';
import sHoc from './sHoc';
const ToolFoot = (props) => {
return (<span>{props.name}</span>)
}
export default sHoc(ToolFoot);
import React, { Component } from 'react';
import ToolHead from 'toolhead';
import ToolFoot from 'toolfoot';
const { Provider } = React.createContext();
class Container extends Component {
constructor(props) {
super(props);
this.state = { name: '小人头' }; // 全局需要引用的状态
}
render() {
return (
<Provider value={this.state}>
<ToolHead />
<ToolFoot />
</Provider>
)
}
}
export default Container;
通常在 Container
容器组件内,拿到所有数据状态,通过 Provider
绑定状态,再根据 sHoc
高阶组件拿到绑定状态
什么是 children 子组件
学过 vue 的都知道在 vue 中有个 slot
插槽用来传递组件内容,而 react 则是通过 children
将组件内容作为其子组件的方式进行传递
const ToolHead = (props) => {
return (<span>{props.name}</span>)
}
const ToolFoot = (props) => {
return (<span>{props.name}</span>)
}
function Root(props) {
return (
<div>{props.children}</div>
)
}
function Container() {
return (
<Root>
<ToolHead name='小人头' />
<ToolFoot name='小人头' />
</Root>
)
}