React|入门之我终于开始学 React 了! 您所在的位置:网站首页 penbeat零基础入门教程 React|入门之我终于开始学 React 了!

React|入门之我终于开始学 React 了!

2023-10-18 07:17| 来源: 网络整理| 查看: 265

最开始接触前端框架的时候,我选择了上手更快的 Vue;到目前为止基于 Vue 开发了几个项目,对于 Vue 原理也有了一定的了解,可以说是“熟练使用”了~🌝 最近项目不太忙,我终于...决定开始学 React 了!!!(搓手、激动、👋🏼)

⤴️ 入门路线:官网入门教程 + 选一本评价还行的 React 书籍 📚

开始前的准备

概览一下 React 特点:【虚拟 DOM、状态、单向数据流、组件】

声明式的视图层 —— JSX,HTML 和 JS 的结合 简单的更新流程 —— 开发者只负责定义 UI 状态,React 负责渲染 灵活的渲染实现 —— 虚拟 DOM 可以结合其他库将其渲染到不同终端 高效的 DOM 操作 —— 虚拟 DOM

先启动一个 React 项目试试:

npm install -g create-react-app // 安装 create-react-app 脚手架 create-react-app my-app // 创建项目 cd my-app/ npm start // 启动

启动有问题:check: create-react-app.dev/docs/gettin…

兼容问题 node 版本切换:

sudo n v14.15.0 // 某个版本号 一、一些基础概念 1、JSX WHAT?

JSX 是 Javascript 的语法扩展,JSX = Javascript + XML,即在 Javascript 里面写 XML,因为 JSX 的这个特性,所以它既具备了 Javascript 的灵活性,同时又兼具 html 的语义化和直观性。

比如:

function Title(){ return Im title~~~; }

在 React 中,JSX 可以生成 React “元素”。【后面会解释什么是 React 元素】

WHY?

React 认为渲染逻辑本质上与其他 UI 逻辑内在耦合,比如,在 UI 中需要绑定处理事件、在某些时刻状态发生变化时需要通知到 UI,以及需要在 UI 中展示准备好的数据。React 通过将二者共同存放在称之为“组件”的松散耦合单元之中,来实现关注点分离。

【不同于 VUE 将 JS 和 HTML 分离开来的方式】

HOW? const name = 'Josh Perez'; const element = Hello, {name}; // {}里面可以写 JS 表达式 ReactDOM.render( element, document.getElementById('root') ); // 表示将 element 这个 JSX 渲染到 id 为 root 的元素上

建议:如果 JSX 有多行的话,用括号括起来。

const element = ( Hello! Good to see you here. );

Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用,创建对应的 DOM 对象:

// 这是简化过的结构 const element = { type: "h1", props: { className: "greeting", children: "Hello, world!", }, };

这些对象被称为 “React 元素”。——【JS对象】

注意:JSX 中写原生 DOM 属性的时候,class 要写成 className,事件名要写成驼峰形式(onclick -> onClick)。

2、元素渲染

要将一个 React 元素渲染到 DOM 节点中,只需把它们一起传入 ReactDOM.render()。如果 UI 要更新,那就需要重新调用 ReactDOM.render()。重新渲染时,React 只会更新变化的部分 —— 虚拟DOM & diff。

3、组件 & props

React 中编写组件有 2 种方式:函数 和 class(类组件)。组件首字母必须大写。

函数组件:【如果组件只有一个 render 方法,没有 state 这些,写成函数组件比较简洁】

function MyComp(props) { return hello {props.name}; }

class组件(等价写法):

// 继承于 React.Component class MyComp extends React.Component { constructor(props) { super(props); // props 是组件接收的参数,super表示执行父类的构造函数,完成初始化 } render() { // render 方法返回需要展示的视图结构——React元素 return hello {this.props.name}; } }

在所有含有构造函数的的 React 组件中,构造函数必须以 super(props) 开头,否则,this.props 在构造函数中可能会出现未定义的 bug。

类组件需满足两个条件:

class 继承自 React.Component; class 内部必须定义 render 方法; function App() { return ( {/* 子组件会通过 this.props.name 接收到 */} ); }

传递给子组件之后,子组件得到了一个 props 对象。

props 是父组件向子组件传递值的形式!它具有只读性:所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。【也就是说我们不能在子组件中直接更改 props 哦!】

React 提供了 PropTypes 对象用于校验 props 的类型。

import PropTypes from "prop-types"; PropTypes.propTypes = { a: PropTypes.object, // a 属性是一个对象类型 b: PropTypes.number, // b 属性是一个数字类型 c: PropTypes.func.isRequired, // 函数类型,必需 }; // defaultProps为属性指定默认值 function Welcome(props) { return Hello, {props.name}; } Welcome.defaultProps = { name: "World", };

组件样式: 外部 CSS 和 内联样式

标签引入:作用于所有组件 import 引入:scoped 局部样式 内联样式: 第一个 {} 表示是 JS 表达式,第二个 {} 表示内部是一个对象,属性名必须使用驼峰式。 4、生命周期

⚠️只有类组件才具有生命周期方法,函数组件是没有的哦~

生命周期具体包括:

挂载阶段,依次调用: constructor() // class 的构造方法,在其中 super(props) 接收参数 componentWillMount() // 组件挂载前调用,实际比较少用到 render() // 组件中定义的方法,返回一个 React 元素,并不负责实际的渲染工作 componentDidMount() // 组件被挂载到 DOM 后调用,比如向后端请求一些数据,此时调用 setState 会引起组件的重新渲染 更新阶段,组件 props 或者 state 变化,依次调用: componentWillReceiveProps(nextProps) // props 变化时调用,nextProps 是新参数 shouldComponentUpdate(nextProps, nextState) // 是否继续执行更新过程,返回一个布尔值 // 通过比较新旧值,如果新旧值相同,该方法会返回 false,后续的更新过程将不再继续,从而优化性能 componentWillUpdate(nextProps, nextState) // 更新之前,比较少用到 render() componentDidUpdate(prevProps, prevState) // 组件更新之后调用,可以操作更新之后的 DOM 了 卸载阶段: componentWillUnmount() { } // 组件被删除前调用,执行一些清理工作 5、state

在组件中可以设置 state 存放组件自己的数据:【是组件私有的数据!】

class MyComp extends React.Component { constructor(props) { super(props); this.state = { time: new Date(), }; } render() { return Its {this.state.time}; } }

不要直接修改 state,而是使用 setState(),React 会【合并】我们设置的 state。每次在组件中调用 setState 时,React 都会自动更新其子组件。

数据流是单向的~只能由父组件流向子组件。

【不可变性】:一般来说,有两种改变数据的方式。第一种方式是直接修改变量的值,第二种方式是使用新的一份数据替换旧数据。React 推荐使用第二种方式,为什么呢?

简化复杂的功能

不可变性使得复杂的特性更容易实现。比如 —— 撤销和恢复功能 在开发中是一个很常见的需求,不直接在数据上修改可以让我们追溯并复用历史记录。

跟踪数据的改变

如果直接修改数据,那么就很难跟踪到数据的改变。跟踪数据的改变需要可变对象可以与改变之前的版本进行对比,这样整个对象树都需要被遍历一次。

跟踪不可变数据的变化相对来说就容易多了。如果发现对象变成了一个新对象,那么我们就可以说对象发生改变了。

确定在 React 中何时重新渲染

不可变性最主要的优势在于它可以帮助我们在 React 中创建 pure components。我们可以很轻松的确定不可变数据是否发生了改变,从而确定何时对组件进行重新渲染。

6、事件处理

React 的事件命名是采用驼峰式,写法是这样的:

Activate Lasers

处理事件的响应函数要以对象的形式赋值给事件属性,而不是字符串形式。因为 React 中的事件是合成事件,不是原生 DOM 事件。

在 React 中,有一个命名规范,通常会将代表事件的监听 prop 命名为 on[Event],将处理事件的监听方法命名为 handle[Event] 这样的格式。

值得注意的是 React 事件处理中的 this 指向问题:

(1)箭头函数

class MyComp extends React.Component { constructor(props) { super(props); this.state = { time: new Date(), }; } handleClick() { console.log(this.state.time); } render() { return ( { this.handleClick(); }} > 按钮 ); } } // this 指向当前组件的实例对象

这种写法,每次 render 调用时都会重新创建一个新的事件处理函数。

(2)组件方法

class MyComp extends React.Component { constructor(props) { super(props); this.state = { time: new Date(), }; this.handleClick = this.handleClick.bind(this); // 通过 bind 将这个方法绑定到当前组件实例 } handleClick() { console.log(this.state.time); } render() { return 按钮; } }

这种写法, render 调用时不会重新创建一个新的事件处理函数,但需要在构造函数中手动绑定 this。

还有一种选择是,我们可以在为元素事件属性赋值的同时绑定 this:

return 按钮

(3)属性初始化语法(ES7)

class MyComp extends React.Component { constructor(props) { super(props); this.state = { time: new Date(), }; } handleClick = () => { console.log(this.state.time); }; // 也是箭头函数 render() { return 按钮; } }

使用官方脚手架 Create React App 创建的项目默认是支持这个特性的,可以在项目中引入 babel 的 transform-class-properties 插件获取这个特性支持。

7、条件渲染 & 列表 & 表单

条件渲染:

function Greeting(props) { const isLoggedIn = props.isLoggedIn; if (isLoggedIn) { return ; } return ; }

列表:

const numbers = [1, 2, 3, 4, 5]; const listItems = numbers.map((number) => {number});

建议给每个动态列表元素加一个 key :【和 Vue 类似】,不然控制台会报错

const listItems = numbers.map((number) => ( {number} ));

表单:如果一个表单元素的值是由 React 管理的,那称它为一个受控组件。

1)文本框 —— 类型为 text 的 input 元素和 textarea 元素,它们受控的主要原理是,通过表单元素的 value 属性设置表单元素的值,通过表单元素的 onChange 事件监听值的变化,并将变化同步到 React 组件的 state 中。

render(){ return ( 名字: ); }

2)select 元素 —— 通过在 select 上定义 value 属性来决定哪一个 option 元素处于选中状态。

render() { return ( 1 2 3 ); } // value 为 2 的元素被选中

3)复选框和单选框 —— type 为 checkbox 和 radio 的 input 元素,React 控制的是 checked 属性。

handleSelectChange(event){ this.setState({ [event.target.name]: event.target.checked }) } render() { return ( 是的 不是 ); }

使用受控组件处理表单状态是比较繁琐的,一种可替代方案时使用非受控组件 —— 表单自己管理状态,React 通过 ref 获取表单的值。这样简化了操作,但是破坏了 React 对组件状态管理的一致性,所以还是少用为好,这里就略过了。

8、状态提升

所谓状态提升,就是将多个子组件需要共同维护的数据提升到父组件中去。

handleChange(e) { this.props.onTemperatureChange(e.target.value); // 触发父组件的事件 // 和 vue 的 this.$emit 类似 }

当你遇到需要同时获取多个子组件数据,或者两个组件之间需要相互通讯的情况时,需要把子组件的 state 数据提升至其共同的父组件当中保存。之后父组件可以通过 props 将状态数据传递到子组件当中。这样应用当中所有组件的状态数据就能够更方便地同步共享了。

9、组合 & 继承

React 推荐使用【组合】方式实现代码复用。Props 和组合为你提供了清晰而安全地定制组件外观和行为的灵活方式。

注意:组件可以接受任意 props,包括基本数据类型,React 元素以及函数。

二、React 哲学

React 最棒的部分之一是引导我们思考如何构建一个应用。

举个例子,假设我们已经拿到了一个设计稿和返回数据的 API:

image.png

第一步:将设计好的 UI 划分为组件层级

在设计稿上用方框圈出每一个组件,包括它们的子组件,并以合适的名字命名。组件划分原则——单一功能原则:一个组件只负责一个功能。

image.png

第二步:用 React 创建一个静态版本

确定了组件层级,可以编写对应的应用了。最好将渲染 UI 和 添加交互这两个过程分开。

先用已有的数据模型渲染一个不包含交互的静态 UI。

第三步:确定 UI state 的【最小且完整】表示

找出应用所需的 state 的最小集合,不要重复或者冗余。【就是说在实现功能的前提下,设置的变量个数尽可能最小】

比如刚刚的示例应用拥有如下数据:

包含所有产品的原始列表 用户输入的搜索词 复选框是否选中的值 经过搜索筛选的产品列表

怎么去 check state 的最小表示呢?问自己三个问题:

该数据是否是由父组件通过 props 传递而来的?如果是,那它应该不是 state。 该数据是否随时间的推移而保持不变?如果是,那它应该也不是 state。 你能否根据其他 state 或 props 计算出该数据的值?如果是,那它也不是 state。

经过检查,刚刚例子中属于 state 的有:

用户输入的搜索词 复选框是否选中的值

第四步:确定 state 放置的位置 —— 哪个组件应该拥有某个 state

React 中的数据流是单向的,并顺着组件层级从上往下传递。

对于应用中的每一个 state:

找到根据这个 state 进行渲染的所有组件。 找到他们的共同所有者(common owner)组件(在组件层级上高于所有需要该 state 的组件)。 该共同所有者组件或者比它层级更高的组件应该拥有该 state。 如果你找不到一个合适的位置来存放该 state,就可以直接创建一个新的组件来存放该 state,并将这一新组件置于高于共同所有者组件层级的位置。

第五步:添加反向数据流

子组件向父组件传值。

【官网这个小例子蛮好的,不管是用什么框架,在开发之前先想清楚要怎么划分组件、怎么设置变量,想好再开始写代码或许事半功倍。】

三、React 16 新特性

🤡 React 16 之前, render 方法必须返回单个元素,现在支持返回 数组【由 React 元素组成】和 字符串。

class Example extends React.Component { constructor(props) { super(props); } render() { return "ssss"; } }

🤡 React 16 之前,组件如果在运行时出错,会阻塞整个应用的渲染,现在有了新的错误处理机制:默认情况下,当组件中抛出错误时,这个组件会从组件树中卸载,从而避免整个应用的崩溃。React 16 还提供了一种更加友好的错误处理方式——错误边界(Error Boundaries),是能够捕获子组件的错误并对其做优雅处理的组件。优雅的处理可以是输出错误日志、显示出错提示等,显然这比直接卸载组件要更加友好。

定义了 componentDidCatch(error, info) 这个方法的组件将成为一个错误边界:

import React from "react"; class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false, }; } componentDidCatch(error, info) { this.setState({ hasError: true, }); console.log(error, info); } render() { if (this.state.hasError) { return OOPS, 出错了!; } return this.props.children; } } export default ErrorBoundary;

使用 ErrorBoundary :

内部组件有异常时,错误会被 ErrorBoundary 捕获,并在界面上显示提示。

🤡 Portals 特性:可以让我们把组件渲染到当前组件树以外的 DOM 节点上。【和 Vue 的 teleport 作用类似】

ReactDOM.createPortal(child, container); // child 是可以被渲染的 React 元素/元素数组/字符串等 // container 是 child 被挂载的 DOM 节点

比如创建一个 Modal 组件:

import React from "react"; import ReactDOM from "react-dom"; class Modal extends React.Component { constructor(props) { super(props); this.container = document.createElement("div"); document.body.appendChild(this.container); } conponentWillUnmount() { document.body.removeChild(this.container); } render() { return ReactDOM.createPortal(我是 Modal, this.container); } } export default Modal;

这样不论在哪里调用该组件,它都是 body 的最后一个子元素。

🤡 React Hooks:使用类组件时,有大量的业务逻辑如各类的接口请求需要放在 componentDidMount 和 componentDidUpdate 等生命周期函数中,会使组件变得特别复杂并且难以维护,并且 Class 中的 this 问题也需要特别注意;函数组件虽然能避免 this 问题,但没有生命周期等。

Hooks 出现之后,可以在函数式组件中去使用 React 的各种特性。

Hooks 本质是一些特殊的函数,常见的有:

🎣 useState:使用 state

const [state, setState] = useState(initState); // 举个例子 import React, { useState } from "react"; function App() { const [state, setState] = useState("Hah"); // 当然这里可以用结构赋值重新命名,whatever u need return ( {state} ); } export default App;

这里 useState 方法同类组件的方法一样,是异步的。但它没有合并多个 state 的作用。

🎣 useRef:使用 ref

import React, { useRef } from "react"; function App() { let el = useRef(); return ( {state} ); // 通过 el.current 可以获得 DOM 节点 } export default App;

🎣 useEffect:处理副作用,诸如网络请求、DOM 操作之类的;相当于 componentDidMount 和 componentDidUpdate 等的集合体。

举个例子:

import React, { useState, useEffect } from "react"; function Course() { const [name, setName] = useState("React"); useEffect(() => { console.log("组件挂载或更新"); return () => { console.log("清理更新前的一些内容"); }; }, [name]); return ( { setName(target.value); }} > React Vue JQuery ); } export default Course;

可以看到,useEffect 接收两个参数,第一个是个函数,第二个是个数组,其中第一个函数返回一个函数,第二个数组表示依赖参数。当依赖参数发生变化时,就会执行回调函数。整个组件的生命周期过程:组件挂载 → 执行副作用(回调函数)→ 组件更新 → 执行清理函数(返还函数)→ 执行副作用(回调函数)→ 组件准备卸载 → 执行清理函数(返还函数)→ 组件卸载。

如果单纯想在某一个特定的生命周期执行某些操作,可以通过传递的参数不同来实现:

只在 componentDidMount 执行,可以把依赖参数置为空数组,这样在更新时就不会执行该副作用了。 只在 componentWillUnmount 执行,同样把依赖参数置为空数组,该副作用的返还函数就会在卸载前执行。 只在 componentDidUpdate 执行,需要区分更新还是挂载,需要检测依赖数据和初始值是否一致,如果当前的数据和初始数据保持一致就说明是挂载阶段,当然安全起见应和上一次的值进行对比,若当前的依赖数据和上一次的依赖数据完全一样,则说明组件没有更新。这种情况需要借助 useRef,原因在于 ref 如果和数据绑定的话,数据更新时 ref 并不会自动更新,这样就可以获取到更新前数据的值。

⚠️只能在函数式组件和自定义 Hooks 之中调用 Hooks,普通函数或者类组件中不能使用 Hooks。

⚠️只能在函数的第一层调用 Hooks。

自定义 Hooks:可以把一些需要重复使用的逻辑自定义成 Hooks,命名必须要以 use 开头。这里暂时不放例子了。

四、深入理解组件 1、state

我们在组件中用到的与渲染无关的变量,应该定义为组件的普通属性,而不应该放在 state 中。也就是说,render 中没有用到的,都不应该出现在 state 中。

注意:

不能直接修改 state,这样不会触发 render。 state 的更新是异步的,而且 React 可能会将多次状态修改合并为一次更新。【Vue 中有相同的机制,很好理解。】props 的更新也是异步的。 state 的更新是一个合并的过程。

React 建议将 state 当作不可变对象,比如当 state 中有数组时,使用 concat、slice、filter 等返回一个新数组的方法,而不是用 push、pop、shift、splice 等直接修改数组的方法。如果有对象时,使用 ES6 的 Object.assign 方法 或者对象扩展语法等。

// eg this.setState(preState => ({ arr: preState.arr.slice(1,3) })) // preState 是旧状态 2、组件与服务器通信 组件挂载阶段通信:componentDidMount 钩子,官方推荐 ✔️ componentDidMount(){ let that = this fetch('/getXXX').then(funtion(response){ that.setState({ data: response.data }) }) }

componentWillMount 钩子会在组件被挂载前调用,也可以从服务端获取数据。如果在服务端渲染,componentWillMount 钩子会被调用两次,而 componentDidMount 钩子 只会被调用一次。所以推荐 componentDidMount 钩子。

组件更新阶段通信:componentWillReceiveProps(nextProps) 钩子 3、组件之间通信 父子组件通信:props

父传子:props 属性,子传父:回调

// 子传父,通过 props 调用父组件的方法 this.props.handleClick(xxx) // 父组件中 兄弟组件通信:状态提升——把共享的状态保存在离它们最近的公共父组件中,核心还是 props Context:组件层级太深时,以 props 为桥梁会很繁琐,React 提供了一个 context 上下文,让任意层级的子组件都可以获得父组件中的状态和方法。创建 context:在提供 context 的组件内新增一个 getChildContext 方法,返回 context 对象,然后在组件的 childContextTypes 属性上定义 context 对象的属性的类型信息。 // 父组件 Father getChildContext(){ return { handleClick: this.handleClick } } // ... Father.childContextTypes = { handleClick: PropTypes.func } // 子组件通过 context 访问 this.context.handleClick() // ... Child.contextTypes = { handleClick: PropTypes.func } 项目复杂时,可以引入 Redux 等状态管理库。 4、特殊的 ref —— 获取 DOM 元素或组件

在 DOM 上使用 ref:

this.textInput.focus() // render 中 { this.textInput = input }}/> // input 表示 input 元素

在组件上使用 ref:【只能是类组件】

this.inputInstance.handleChange() // 调用通过 ref 获取的 inputInstance 组件实例的 handleChange 方法 // render 中 { this.inputInstance = input }}/> // 这里 input 表示组件实例 五、虚拟 DOM 和性能优化 1、虚拟 DOM —— JS 对象

前端性能优化中有一个原则:尽量减少 DOM 操作,而虚拟 DOM 正是这一原则的体现。

DIFF 算法:对比新旧虚拟 DOM,时间复杂度 O(n)。

前提假设:

(1)如果两个元素的类型不同,那么它们将生成两棵不同的树。

(2)为列表中的元素设置 key 属性,用 key 标识对应的元素在多次 render 过程中是否发生变化。

具体来说:

当根节点是不同类型,不会继续比较子节点,直接按照新的虚拟 DOM 生成真实 DOM; 根节点是相同的 DOM 类型,保留根节点,更新变化了的根节点属性; 根节点是相同的组件类型,对应的组件实例不会被销毁,只会执行更新操作; 比较完根节点, React 会以相同的原则递归对比子节点; React 给列表提供了一个 key 属性,用来复用列表。 2、性能优化 避免不必要的组件渲染,善用 shouldComponentUpdate 钩子,根据具体业务逻辑决定返回 true 或 false; 使用 key; React Developer Tools for Chrome 插件 why-did-you-update 插件

image.png

六、高阶组件(HOC) —— 组件逻辑的抽象和复用

-- HighOrderComponent

1、基本概念

高阶函数:以函数为参数,返回值也是函数的函数。类似地,高阶组件就是以 React 组件为参数,返回一个新的 React 组件的组件。

和父组件有啥区别呢?高阶组件强调的是逻辑的抽象。高阶组件是一个函数,函数关注的是逻辑;父组件是一个组件,组件主要关注的是 UI/DOM。如果逻辑是与 DOM 直接相关的,那么这部分逻辑适合放到父组件中实现;如果逻辑是与 DOM 不直接相关的,那么这部分逻辑适合使用高阶组件抽象,如数据校验、请求发送等。

比如我们写一个 MyComp 组件,来获取 localStorage 中的数据并显示:

class MyComp extends React.Component { constructor(props) { super(props); } componentWillMount() { let data = localStorage.getItem("data"); this.setState({ data }); } render() { return {this.state.data}; } }

如果其他组件也有这样的逻辑时,试试复用:

// 高阶组件 function HocComp(OtherComp) { return class extends React.Component { componentWillMount() { let data = localStorage.getItem("data"); this.setState({ data }); } render() { return ; } }; } class MyComp extends React.Component { render() { return {this.props.data}; } } const MycompWithOther = HocComp(MyComp);

可以看出高阶组件的主要功能:封装并分离组件的通用逻辑,让通用逻辑在组件中更好地被复用 —— 装饰者设计模式【🚩:后面看一下设计模式】。

2、使用场景 1)操纵props

前面的例子

2)通过 ref 访问组件实例 function withRef(WrappedComp) { return class extends React.Component { constructor(props) { super(props); this.someMethod = this.someMethod.bind(this); } // 这里保存了 WrappedComp 实例的引用 someMethod() { this.instance.methodOfWrappedComp(); } render() { return ( { this.instance = instance; }} {...this.props} /> ); } }; } 3)组件状态提升

比如利用高阶组件将原本受控组件需要自己维护的状态统一提升到高阶组件中。

4)用其他元素包装组件

比如增加布局或者修改样式:

function withRedBg(WrappedComp) { return class extends React.Component { render() { return ( ); } }; } 3、参数传递

高阶组件除了接收组件作为参数外,还可以接收其他参数。比如:

// 高阶组件 function HocComp(OtherComp, key) { return class extends React.Component { componentWillMount() { let data = localStorage.getItem(key); this.setState({ data }); } render() { return ; } }; } class MyComp extends React.Component { render() { return {this.props.data}; } } const MycompWithOther = HocComp(MyComp, "data"); // 或者 const MycompWithOther = HocComp(MyComp, "username"); 4、继承方式实现高阶组件

继承方式实现的高阶组件常用于渲染劫持。例如,当用户处于登录状态时,允许组件渲染;否则渲染一个空组件:

function withAuth(WrappedComp) { return class extends WrappedComp { render() { if (this.props.isLogin) { return super.render(); } else { return null; } } }; } 七、项目实战之 Router 和 Redux 1、React Router 基本用法

React Router 包含3个库:react-router、react-router-dom 和 react-router-native。

react-router 提供最基本的路由功能,实际使用时,根据应用运行的环境选择安装 react-router-dom(在浏览器中使用)或react-router-native(在 react-native 中使用)。 react-router-dom 和 react-router-native 都依赖于 react-router,所以在安装时,react-router 也会自动安装。

在 Web 应用中安装 react-router-dom:

npm install react-router-dom

React Router 通过 Router 和 Route 两个组件完成路由功能。Router 可以理解成路由器,一个应用中只需要一个 Router 实例,所有的路由配置组件 Route 都定义为 Router 的子组件。【和 VueRouer 类似】

在 Web 应用中,一般会根据路由的实现方式分为:

BrowserRouter:基于 H5 的 history API, HashRouter:基于 hash

Route 是 React Router 中用于配置路由信息的组件。

import { Route } from 'react-router' // path 匹配路径,匹配之后会创建一个 match 对象作为 props 中的一个属性传递给被渲染的组件 // 匹配时,渲染 Home 组件 // 也可以写为 { } } />

如果只想让匹配到的第一个 Route 渲染,可以使用 Switch:

import { BrowserRouter as Router, // 注意这里 Switch, Route, Link } from "react-router-dom"; // 比如首页导航,exact 精确匹配 // 不使用 Switch 的话,/about/user 这个路径会匹配上面三个 Route,都会被渲染

Link 组件定义了点击时页面如何路由:

登录 // to 也可以是一个对象

Redirect 组件用于页面重定向:

在非路由组件中获取路由信息:withRouter 和 Router Hooks。

withRouter 的作用有点类似于 Redux 中的 connect,把要获取路由信息的组件传入 withRouter,withRouter 会把路由信息传递给该组件,并会返回一个新的组件,来方便其他地方调用。

import React from "react"; import { withRouter } from "react-router-dom"; function backBtn(props) { let { history } = props; return ( { history.goBack(); }} > 返回上一页 ); } backBtn = withRouter(backBtn); export default backBtn;

除了使用 withRouter 来为非路由组件获取路由信息之外,在Router5.x中新增加了 Router Hooks,使用规则和 React 的其他 Hooks 一致。

1)useHistory: 调用该 Hook 会返回 history 对象

2)useLocation:调用该 Hook 会返回 location 对象

3)useRouteMatch:调用该 Hook 会返回 match 对象

4)useParams:调用该 Hook 会返回 match 对象中的 params,也就是 path 传递的参数

2、Redux 基本用法 —— 状态管理机,和 Vuex 类似

比如我们有一个 TODO 事项的数据对象:

{ todoList: [{ text: '吃饭', finished: false },{ text: '睡觉', finished: false },{ text: '打豆豆', finished: false }, ], visibilityFilter: 'SHOW_FINISHED' }

如果我们需要修改数据,必须发送一个 action:

{ type: 'ADD_TODO', text: '运动' }

type 表示 action 的类型,这是 action 必须包含的字段,其他的不是确定的。如何解析 action 呢?Redux 利用 reducer 解析 action,reducer 是一个普通的 JS 函数,接收 action为参数,返回一个新的应用状态 state。

function todoApp(state = {}, action) { switch (action.type) { case "ADD_TODO": // return new state case "XXXX": // return new state default: return state; } }

以上就是 Redux 的基础概念:store【数据容器】、state【数据对象】、action【修改命令】、reducer。

Redux 应用需要遵循三大原则:

唯一数据源:只维护一个全局的状态对象,存储在 Redux 的 store 中 保持应用状态只读:不能直接修改状态,而是基于 action 修改 状态的改变通过纯函数完成:action 表示修改状态的意图,真正执行的是 reducer,reducer 必须是纯函数

【PS:所谓纯函数,就是:对于同样的输入,函数总是有同样的输出,且函数的执行不会产生副作用,比如修改外部对象或输出到 I/O 设备】

1)action:通过 store 的 dispatch 方法分发 // action creater,返回 action function addTodo(text) { return { type: "ADD_TODO", text, }; } 2)reducer:描述应用发生了什么操作,根据 action 做出响应 // reducer function todoApp(state = {}, action) { switch (action.type) { case "ADD_TODO": return { ...state, todoList: [ ...state.todoList, { text: action.text, finished: false, }, ], }; default: return state; } } 3)store:对象: 保存状态 通过 getState() 访问状态 通过 dispatch(action) 发送更新状态的 action 通过 subscribe(listener) 注册监听函数,监听状态改变 import { createStore } from 'redux' function todoApp(){} const store = createStore(todoApp, initState) // 获取状态 const state = store.getState() // 发送 action store.dispatch(addTodo('吃饭')) // 注册监听函数 let listener = store.subscribe(()=>{ console.log(store.getState()) // 状态更新时,获取最新的状态 }) // 取消监听,直接调用 store.subscribe 返回的函数即可 listener()

Redux 的数据流过程:

(1)调用 store.dispatch(action)。一个 action 是一个用于描述“发生了什么”的对象。store.dispatch(action) 可以在应用的任何地方调用,包括组件、XHR 的回调、定时器。

(2)Redux 的 store 调用 reducer 函数。store 传递两个参数给 reducer:当前应用的状态和 action。reducer 必须是一个纯函数,它的唯一职责是计算下一个应用的状态。

(3)根 reducer 会把多个子 reducer 的返回结果组合成最终的应用状态。根 reducer 的构建形式完全取决于用户。Redux 提供了 combineReducers,方便把多个拆分的子reducer 组合到一起,但完全可以不使用它。当使用 combineReducers 时,action 会传递给每一个子 reducer 处理,子 reducer 处理后的结果会合并成最终的应用状态。

(4)Redux 的 store 保存根 reducer 返回的完整应用状态。此时,应用状态才完成更新。如果 UI 需要根据应用状态进行更新,那么这就是更新 UI 的时机。对于 React 应用而言,可以在这个时候调用组件的 setState 方法,根据新的应用状态更新 UI。

image.png

使用 Redux:

npm install react-redux

根据意图的不同,组件可以分为容器组件和展示组件:

容器组件:负责逻辑 展示组件:负责 UI

react-redux 提供了一个 connect 函数,用于把 React 组件和 Redux 的 store 连接起来,生成一个容器组件,负责数据管理和业务逻辑:

import { connect } from "react-redux"; // 这里有个组件 TodoList const VisibleTodoList = connect()(TodoList); // 创建了一个容器组件 // 传递两个参数,让这个容器组件同步状态变化 const VisibleTodoList = connect(mapStateToProps, mapDispatchToProps)(TodoList); // mapStateToProps 和 mapDispatchToProps 都是函数

react-redux 提供了一个 Provider 组件:

// 示意代码 class Provider extends React.Component { getChildContext() { return { store: this.props.store }; } render() { return this.props.children; } } Provider.childContextTypes = { store: React.PropTypes.object, }; // 把 store 保存到 context

Provider 组件通过 context 把 store 传递给子组件,所以一般将 Provider 组件用作根组件:

import { createStore } from "redux"; import { Provider } from "react-redux"; render( , document.getElementById("root") ); 3、React + Redux

1)如何组织项目结构?

按照类型:components、containers、actions等,同类型的文件组织在一起 按照页面功能:一个页面功能对应一个文件夹,里面包括 components、action等 Ducks:github.com/erikras/duc… 以应用的状态作为划分模块的依据

2)设计 state:像设计数据库一样设计 state

把整个应用的状态按照领域分成若干子状态,子状态之间不能保存重复的数据 state 以键值对的结构存储数据,以记录的 key 或 ID 作为记录的索引,记录中的其他字段都依赖于索引 state 中不能保存可以通过 state 中的已有字段计算而来的数据,即 state 中的字段不互相依赖 八、总结

至此算是完成了 React 入门,在了解 React 的过程中,感受之一是 React 对于 JavaScript 基础的要求更高一些,比如 this 指向、高阶函数、class 这些,而且抽象程度也更高,比如高阶组件、Redux 中间件等等,其中每一个点都值得深挖学习。

Anyway,总算开始学习 React 了,“框架是相通的”这句话不假,有 Vue 的使用经验能在很大程度上帮助我对 React 的理解。后续打算写一写小 demo 增加对 React 的熟练度,同时慢慢了解下原理性的东西。🔑



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有