四:以理论结合实践方式梳理前端 React 框架 ——— React 高级语法

本文介绍了React中内置组件的事件处理机制,包括如何阻止默认行为、使用事件对象及事件池。此外,还详细讲解了React的条件渲染、列表渲染以及状态提升的概念和实践。文中提到了虚拟DOM的优势,并探讨了高阶组件的应用,最后讨论了组件形态的分类,如傀儡组件、状态组件、容器组件等。
摘要由CSDN通过智能技术生成

事件处理

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 是一种单向性数据流进行组件之间的传值的,但对于代数多的组件传值,就显得非常麻烦.

props
props
props
props
props
props
父组件
子组件
孙组件
重孙组件
重重孙组件
重重重孙组件
第N代孙组件

兄弟组件之间的状态进行传递,通常采用的方式将两者的状态放到父组件内,这种跨度传值,不符合 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>)
	}
}

通过 ToolHeadToolFoot 可以看出,使用 App 组件的状态,无需通过 props 传递获取,同理,不管是第 N 代都可以使用这种方式

不推荐使用这种方式引用,每次构造起来较为复杂,后续采用 redux 来进行状态管理结合 props 来开发实际业务

组件形态

基于组件声明的方式及其应用场景,分为如下五种组件:

  1. 傀儡组件:纯静态的将接收数据展示到界面上;
  2. 状态组件:处理不同时刻下更新状态,带有生命周期;
  3. 容器组件:用户获取数据和业务逻辑处理;
  4. 高阶组件:会返回组件的组件,类似插件,处理校验、获取数据;
  5. 渲染回调:将渲染逻辑委托给其子组件【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>
	)
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值