react核心概念及其应用
第1章:React入门
1.1 React基础认识
1.1.1 官方资料
React是什么?React是一个声明式,高效且灵活的用于构建用户界面的 JavaScript 库(只关注于View层)。使用 React 可以将一些简短、独立的代码片段组合成复杂的 UI 界面,这些代码片段被称作“组件”。
1.1.2 React的特点
- Declarative 声明式: 以声明式编写UI,让代码更加可靠且方便调试
- Component-Based 组件化: 创建拥有各自状态的组件,再由组件组成更加复杂的UI
- Learn Once, Write Anywhere 一次学习,随处编写:无论什么技术栈,无需重写现有代码,引入React即可开发新功能,同时还可以进行服务器端渲染
- 高效: (1)虚拟DOM,不直接操作DOM (2)DOM Diff算法,最小化页面重绘
- 单向数据流: 父子组件采用props传递数据
1.2 React基本使用
如何将 React 组件添加到现有的 HTML 页面中。你可以基于自己现有的网站,或创建一个空的 HTML 文件来练习。
1.2.1 步骤 1:添加一个 DOM 容器到 HTML
首先,打开你想要编辑的 HTML 页面。添加一个空的 <div>
标签作为标记你想要用 React 显示内容的位置。例如:
<!-- ... 其它 HTML ... -->
<div id="like_button_container"></div>
<!-- ... 其它 HTML ... -->
我们给这个 <div>
加上唯一的 id
HTML 属性。这将允许我们稍后用 JavaScript 代码找到它,并在其中显示一个 React 组件。React 会替换 DOM 容器内的任何已有内容。
1.2.2 步骤 2:添加 Script 标签
接下来,在 </body>
结束标签之前,向 HTML 页面中添加3个 <script>
标签, 前两个标签加载 React。第三个将加载组件代码:
<!-- ... 其它 HTML ... -->
<!-- 加载 React。-->
<!-- 注意: 部署时,将 "development.js" 替换为 "production.min.js"。-->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<!-- 加载我们的 React 组件。-->
<script src="like_button.js"></script>
1.2.3 步骤 3:创建一个 React 组件
在 HTML 页面文件的同级目录下创建一个名为 like_button.js
的文件。在 like_button.js
的底部,加入以下两行代码。
const e = React.createElement
const domContainer = document.querySelector('#like_button_container');
ReactDOM.render(e('button',
{ onClick: () => this.setState({ liked: true }) },
'Like'), domContainer);
这两行代码会找到我们在步骤 1 中添加到 HTML 里的 <div>
,然后在它内部显示我们的 React 组件 “Like” 按钮。
1.2.4 步骤 4:使用 React 和 JSX
在上面的示例中,我们只依赖了浏览器原生支持的特性。这就是为什么我们使用了 JavaScript 函数调用来告诉 React 要显示什么, 但是,React 还提供了一种用 JSX 来代替实现的选择:
// 显示一个 "Like" <button>
return (
<button onClick={() => this.setState({ liked: true })}>
Like
</button>
);
在项目中尝试 JSX 需要使用到babel
, JSX代码所在的<script>
标签需要添加type="text/babel"
属性:
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
但是这种引入方式不能使用在生产环境中。本节只是一个简单的原生示例,实际中项目我们都是推荐使用集成的工具链来实现最佳的用户和开发人员体验。
1.3 React JSX 以及虚拟DOM
1.3.1 虚拟DOM概念
- React提供了一些API来创建“特别”的js对象,如上文应用的React.createElement方法
- 虚拟DOM对象最终都会被React转换为真实的DOM并渲染。我们在写代码时关注点都在虚拟DOM的相关数据上
将一个元素渲染为DOM时,需要一段html声明根节点,然后传入只需把它们一起传入 ReactDOM.render()
// HTML片段,DOM根节点
<div id="root"></div>
// JS片段
const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById('root'));
render
方法的两个参数:纯js或jsx创建的虚拟DOM对象,以及用来包含虚拟DON元素的根节点对象
1.3.2 JSX相关概念
JSX——JavaScript XML,是React定义的一种类似于XML的拓展语法,其作用是用来创建React虚拟DOM对象JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式。JSX 可能会使人联想到模版语言,但它具有 JavaScript 的全部功能。React 并没有采用将标记与逻辑进行分离到不同文件这种人为地分离方式,而是通过将二者共同存放在称之为“组件”的松散耦合单元之中,来实现关注点分离。
const element = <h1>Hello, world!</h1>;
语法规则:
- 遇到< 开头的代码,以标签语法解析,html同名标签转换为html同名元素,其他标签需要特别解析,往往是组件
- 遇到以{开头的代码,以JS语法解析。
- 由于 JSX 会编译为 React.createElement 调用形式,所以 React 库也必须包含在 JSX 代码作用域内。即使没有显式地调用React及其组件,依然需要引入它
- 用户自定义的组件必须的大写字母开头
一个JSX语法片段只能返回一个根元素,所以必须有外层元素包裹。如果需要返回多个元素,Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。短语法是
<></>
<React.Fragment> <td>Hello</td> <td>World</td>
布尔类型、Null 以及 Undefined 将会忽略, 以下元素渲染结果相同
<div /> <div></div> <div>{false}</div> <div>{null}</div> <div>{undefined}</div> <div>{true}</div>
这有助于在特定条件下渲染元素
<div> {showHeader && <Header />} <Content /> </div>
JSX本身也是表达式,在编译之后,JSX 表达式会被转为普通 JavaScript 函数调用,并且对其取值后得到 JavaScript 对象。可以:
- 在 if 语句和 for 循环的代码块中使用 JSX
- 将 JSX 赋值给变量
- 把 JSX 当作参数传入
从函数中返回 JSX
JSX中可以通过使用引号,来将属性值指定为字符串字面量:
const element = <div tabIndex="0"></div>;
因为 JSX 语法上更接近 JavaScript 而不是 HTML,所以 React DOM 使用
camelCase
(小驼峰命名)来定义属性的名称,而不使用 HTML 属性名称的命名约定。1.4 模块与组件和模块化与组件化的理解
1.4.1 模块
- 理解:向外提供特定JS功能的程序,一般是一个JS文件
- 为什么需要模块:现代化前端工程,尤其是大型工程,JS代码更多更复杂,需要更好的机制去优化开发环节的便利以及生产环节的加载性能
作用:提高JS的复用能力,提高编程效率,提高运行效率
1.4.2 组件
- 理解: 组件是用来实现特定功能效果的代码集合,包括结构、样式与行为(HTML/CSS/JS)
- 为什么需要组件:现代前端工程中一个界面的功能更加复杂,需要进行拆分并关注当前最小单元
- 作用:提高JS的复用能力,提高编程效率,提高运行效率
React的组件分为:(1)继承React的class类组件(2)函数组件
第2章:React面向组件编程
2.1 基本理解和使用
2.1.1 自定义组件
// 方式一:工厂函数组件 const MyFactoryComp = (props) => { return <h1>函数组件</h1> } // 方式二:ES6类组件 class MyClassComp extends React.Component { render () { return <h1>类组件</h1> } }
// 渲染组件 ReactDom.render(<MyFactoryComp />, document.getElementById('root'))
2.1.2 注意事项
- 组件名首字母必须大写,没有大写的在引用之前都需要先赋予首字母大写的别名,然后再引用别名
- 虚拟DOM元素只能有一个根元素,如果不方便插入根元素可使用
<></>
虚拟DOM元素必须有结束标签
2.1.3 渲染过程
- React创建内部虚拟DOM对象
- 将其解析为真实的DOM片段
插入到指定元素中,并替换元素下所有内容,所以
#root
元素节点的内容写了也是无效的2.2 组件三大属性之state
2.2.1 定义及定义方法
函数组件没有
state
,类组件才可以维护state
,但是函数组件也有自己的hook来进行内部数据管理。类组件中有两种方式声明state
并进行初始赋值:在
class
构造函数constructor
中进行初始赋值class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } }
直接定义
state
对象变量class Clock extends React.Component { state = {date: new Date()} render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } }
2.2.2 示例
class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } componentDidMount() { this.timerID = setInterval( () => this.tick(), 1000 ); } componentWillUnmount() { clearInterval(this.timerID); } tick() { this.setState({ date: new Date() }); } render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } } ReactDOM.render( <Clock />, document.getElementById('root') );
如何理解上述代码的调用顺序
- 当 <Clock /> 被传给
ReactDOM.render()
的时候,React 会调用 Clock 组件的构造函数。此时第一次更新state
- 之后 React 会调用组件的
render()
方法。组件第一次被渲染 - 当 Clock 的输出被插入到 DOM 中后,React 就会调用
ComponentDidMount()
生命周期方法。设置tick()
方法 - 每秒都会调用一次
tick()
方法。更新setState()
,重新调用render()
方法 组件被移除时,调用
componentWillUnmount()
生命周期方法,计时器被移除2.2.3 注意事项
- 当 <Clock /> 被传给
- 不要直接修改state,需要使用
setState()
,这个函数可以接收一个对象,也可以接收一个函数,构造函数和声明处是唯一可以直接给state
赋值的地方 state
的更新可能是异步的,不能依赖他们的值来更新下一个状态,或者在代码后立即使用新值做一些事情// Correct this.setState((state, props) => ({ counter: state.counter + props.increment }));
state
的更新会被合并,当你调用setState()
的时候,React 会把你提供的对象合并到当前的 state2.3 组件三大属性之props
2.3.1 定义及定义方法
组件的概念类似于JS函数,该函数可以理解为:入参——即组件标签上的所有属性(attributes)以及子组件(children)转换为单个对象传递给组件,即props,返回值:返回React元素
在函数组件中,props是写明的唯一参数,在类组件中,props可以在组件内部直接引用2.3.2 示例
// 函数组件举例 function Welcome(props) { return <h1>Hello, {props.name}</h1>; } // 类组件举例 class Welcome extends React.Component { render() { return <h1>Hello, {this.props.name}</h1>; } } // 组件调用 const element = <Welcome name="Sara" />; ReactDOM.render( element, document.getElementById('root') );
上例中,
name="Sara"
属性被赋予了组件,就会被转换为{name: 'Sara'}
作为props
传递给组件2.3.3 注意事项
props是只读的,无论是函数组件还是类组件,都不能改变自身的props,所有组件必须像维护纯函数一样保证props不被修改。
2.4 组件三大属性之refs
2.4.1 定义及定义方法
在某些情况下,你需要在典型数据流之外强制修改子组件。如获取已渲染元素在屏幕上宽高、手动给input聚焦等,React提供了refs来获取真实的DOM。
refs需要像state一样进行初始赋值2.4.2 示例
Refs 是使用
React.createRef()
创建的,并通过ref
属性附加到 React 元素。在构造组件时,通常将 Refs 分配给实例属性,以便可以在整个组件中引用它们。class MyComponent extends React.Component { constructor(props) { super(props); this.myRef = React.createRef(); // 将 Refs 分配给实例属性, 创建一个 ref 来存储 myRef 的 DOM 元素 } componentDidMounted () { // 对该节点的引用可以在 ref 的 current 属性中被访问 const node = this.myRef.current; ... } render() { return <div ref={this.myRef} />; } }
2.4.3 注意事项
- 如果一件事情可以通过声明式来完成,那最好不要使用
refs
refs
只能在类组件中使用,不能在函数组件中使用,因为它没有实例ref
会在componentDidMount
或componentDidUpdate
生命周期钩子触发前更新。挂载前,给current
属性传入DOM元素,卸载前,传入null
元素2.5 事件处理
2.5.1 示例
class Toggle extends React.Component { constructor(props) { super(props); this.state = {isToggleOn: true}; // 为了在回调中使用 `this`,这个绑定是必不可少的 this.handleClick = this.handleClick.bind(this); } handleClick() { this.setState(state => ({ isToggleOn: !state.isToggleOn })); } render() { return ( <button onClick={this.handleClick}> {this.state.isToggleOn ? 'ON' : 'OFF'} </button> ); } } ReactDOM.render( <Toggle />, document.getElementById('root') );
2.5.2 注意事项
通过
onXxx
(注意驼峰式)属性指定组件的事件处理函数(函数而非字符串)// 原生事件 <button onclick="activateLasers()"> Activate Lasers </button> // React事件 <button onClick={activateLasers}> Activate Lasers </button>
不能通过返回
false
的方式阻止默认行为。你必须显式的使用preventDefault
。function ActionLink() { function handleClick(e) { e.preventDefault(); console.log('The link was clicked.'); } return ( <a href="#" onClick={handleClick}> Click me </a> ); }
- 参数
e
是一个合成事件,并且作为默认第一个参数传递,可以在回调函数内直接调用 需要谨慎对待事件回调函数中的
this
,class
的方法默认不会绑定this
,如果函数内部需要调用类组件的this
, 需要显式绑定或者采用箭头函数声明回调函数2.6 组件生命周期
2.6.1 生命周期定义
生命周期是组件对象从创建到销毁所经历的特定的生命周期阶段。React组件对象定义了一系列的钩子函数,可以在生命周期的特定时刻执行这些函数。我们在定义组件中可以重写生命周期钩子函数,达到我们自己的目的。
2.6.2 生命周期流程
进行细分可以分为:阶段 调用方法 挂载阶段 constructor()
/static getDerivedStateFromProps()
/render()
/componentDidMount()
更新阶段 static getDerivedStateFromProps()
/shouldComponentUpdate()
/render()
/getSnapshotBeforeUpdate()
/componentDidUpdate()
卸载 componentWillUnmount()
错误处理 static getDerivedStateFromError()
/componentDidCatch()
2.6.3 注意事项
render()
方法是class
组件中唯一必须实现的方法- 在
constructor()
函数中不要调用setState()
方法, 要避免在构造函数中引入任何副作用或订阅 - 避免将 props 的值复制给 state
componentWillUnmount()
中需要卸载具有副作用的一些方法,比如定时器、取消网络请求、清除挂载时的订阅等2.7 HOOK
ES6 class
组件可以在内部定义state
进行数据管理,函数组件现在也有了类似功能的Hook
,可以在不编写class
的情况下使用state
以及其他的React特性。以前我们说,复杂组件用类组件,函数组件只适合简单组件,有了Hooks
支撑之后,函数组件也可以向类组件一样实现更加复杂的功能。
Hook 是一些可以让你在函数组件里“钩入” Reactstate
及生命周期等特性的函数。其中最常用的就是State Hook
和Effect Hook
,前者提供了useState
方法向组件内部添加一些state
,后者则提供了useEffect方法来模拟componentDidMount
、componentDidUpdate
和componentWillUnmount
生命周期。2.7.1 State Hook
示例
// 类组件写法 class Example extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } render () { return ( <> <button onClick={() => this.setState({ count: this.state.count + 1 })}> Click me </button> <p>You clicked {this.state.count} times</p> </> ) } } // 函数组件写法 import React, { useState } from 'react'; function Example() { // 声明一个叫 “count” 的 state 变量 const [count, setCount] = useState(0); const [fruit, setFruit] = useState('banana'); return <> <button onClick={() => setCount(count + 1)}> Click me </button> <p>You clicked {count} times</p> </> )
- 引入React中的
useState
- 通过调用
useState
声明一个state
变量,该函数返回一对值,通过方括号进行数组解构赋值,我们定义两个命名变量,第一个解构变量是state
变量名,第二个解构变量是对该state
变量进行更新的函数 - 同类组件中的
state
变量一样,我们不能直接修改它,需要使用上述第二个解构变量进行间接修改它(如同类组件中的setState
一样) - 函数组件内没有
this
,我们在引用变量的时候,直接使用上述第一个命名解构变量即可,无需使用this.Xxx
多个
state
变量可以进行多次进行声明和调用,与类组件中不同的是,对不同的state
变量进行更新时,不再是合并,而是直接替换目标变量2.7.2 Effect Hook
示例
import React, { useState, useEffect } from 'react'; function Example() { const [count, setCount] = useState(0); // Similar to componentDidMount and componentDidUpdate: useEffect(() => { // Update the document title using the browser API document.title = `You clicked ${count} times`; }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
useEffect Hook
有两个参数,第一个参数是一个带有副作用的函数,在DOM更新之后会调用它,即componentDidMount
、componentDidUpdate
生命周期钩子之后。第二个参数是内部useState
和函数组件参数——即props
中若干变量组成的数组,如果省略第二个参数,则意味着每次更新渲染之后都会执行这个useEffect Hook
。如果我们的useEffect Hook
只依赖于部分变量的修改,可以在数组中订阅它。如果只想在挂载和卸载时执行,传入空数组[]
就好了。有些
useEffect Hook
是需要被手动清除的。当订阅外部数据源,清除工作非常重要,可以防止引起内存泄露。WHY???当然是闭包。useEffect Hook
返回一个函数,React 将会在执行清除操作时调用它,相当于componentWillUnmount
生命周期调用。import React, { useState, useEffect } from 'react'; function FriendStatus(props) { const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); // Specify how to clean up after this effect: return function cleanup() { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); if (isOnline === null) { return 'Loading...'; } return isOnline ? 'Online' : 'Offline'; }
2.7.3 其他常用Hook
useCallback
返回一个memoized
回调函数。const memoizedCallback = useCallback( () => { doSomething(a, b); }, [a, b], );
把内联回调函数及依赖项数组作为参数传入
useCallback
,它将返回该回调函数的memoized
版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如shouldComponentUpdate
)的子组件时,它将非常有用。useMemo
返回一个memoized
值。const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
把“创建”函数和依赖项数组作为参数传入
useMemo
,它仅会在某个依赖项改变时才重新计算memoized
值。这种优化有助于避免在每次渲染时都进行高开销的计算。useReducer
useRef
useImperativeHandle
useLayoutEffect
useDebugValue
自定义Hook
2.7.4 注意事项
- 只在最顶层使用Hook, 不要在循环、条件或嵌套函数中调用Hook
只在React函数中调用Hook,不要在普通的JavaScript函数只能怪调用Hook,可以在React的函数组件中调用或在自定义Hook中调用
第3章:React应用(基于React脚手架)
3.1 使用脚手架工具创建React应用
3.1.1 脚手架介绍
React官网推荐使用
Create React App
创建一个新的单页应用。如果是大型复杂的前端项目,我们也可以自己从0开始搭建——脚手架工具包含三大组件:package管理器、打包器、编译器,所以我们可以用npm + webpack + babel组件自己的工具。
脚手架工具可以给我们带来的便利包括:- 拓展项目和组件的规模,以工程化的思维管理项目,模块化、组件化、工程化
- 包含了项目所需的基本配置
- 指定好了基础依赖
- 提供了一个最简单demo可以直接查看效果
清晰的文件管理目录
3.1.2 创建项目并启动
Create React App
会配置你的开发环境,以便使你能够使用最新的JavaScript
特性,提供良好的开发体验,并为生产环境优化你的应用程序。npx create-react-app my-app cd my-app npm start
3.1.3 理解项目结构
3.2 demo
第4章:开发中若干要点
4.1 组件间通信
4.1.1 方式一:通过props传递
- 共同的数据放在父组件上,特有的数据放在内部作为
state
维护 通过
props
可以传递一般数据和函数,一层一层传递一般数据是由父组件传递给子组件,子组件进行数据读取,而函数经常被用来在子组件中调用并传递数据给父组件。
4.1.2 方式二:使用消息发布订阅机制
可以借助PubSub库来实现
4.1.3 方式三: Context
Props
属性是自上而下传递的,但当一个属性是应用程序中许多组件都需要的时候,显式地通过组件树层层传递就显得繁琐,Context
提供了一个无需为每层组件手动添加props
,就能在组件树间进行数据传递的方法。Context
设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。props
代码示例:class App extends React.Component { render() { return <Toolbar theme="dark" />; } } function Toolbar(props) { // Toolbar 组件接受一个额外的“theme”属性,然后传递给 ThemedButton 组件。 // 如果应用中每一个单独的按钮都需要知道 theme 的值,这会是件很麻烦的事, // 因为必须将这个值层层传递所有组件。 return ( <div> <ThemedButton theme={props.theme} /> </div> ); } class ThemedButton extends React.Component { render() { return <Button theme={this.props.theme} />; } }
Context
代码示例// 使用Context // Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。 // 为当前的 theme 创建一个 context(“light”为默认值)。 const ThemeContext = React.createContext('light'); class App extends React.Component { render() { // 使用一个 Provider 来将当前的 theme 传递给以下的组件树。 // 无论多深,任何组件都能读取这个值。 // 在这个例子中,我们将 “dark” 作为当前的值传递下去。 return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); } } // 中间的组件再也不必指明往下传递 theme 了。 function Toolbar() { return ( <div> <ThemedButton /> </div> ); } class ThemedButton extends React.Component { // 指定 contextType 读取当前的 theme context。 // React 会往上找到最近的 theme Provider,然后使用它的值。 // 在这个例子中,当前的 theme 值为 “dark”。 static contextType = ThemeContext; render() { return <Button theme={this.context} />; } }
注意事项:
Context
主要应用场景在于很多不同层级的组件需要访问同样一些的数据。请谨慎使用,因为这会使得组件的复用性变差。4.1.4 方式四:Redux
Redux提供一种全局的应用程序状态管理工具,可以像
vue
中的vuex
一样方便地管理共享状态。后面会详细介绍。4.2 异步请求
React本身只关注于界面,并不包含
ajax
请求,当需要前后台交互时,需要集成第三方ajax
库。axios
和fetch
是较为常用的ajax
请求库:axios
: 封装了XmlHttpRequest
对象的ajax
的轻量级工具,支持promise
,浏览器端和服务端均可使用fetch
: 是新版浏览器支持的原生函数,老版本浏览器并不支持,需要支持老版本的话可添加fetch.js
,不再使用XmlHttpRequest
对象提交ajax
4.3 ES6及TypeScript
ES6: 由于React类组件是采用
ES6 class
定义的,所以我们需要在一开始就配合babel
使用。
当项目越来越大时,增加静态类型检查器可以在运行前识别某些类型错误。React中提供了PropTypes
来限定props
的输入类型,但其实在函数输入输出、state
管理、接口统一规范等各处都需要进行静态类型检查。
建议在大型代码库中使用Flow
或TypeScript
来代替,尤其是TypeScript
。4.4 开源UI组件库
- material-ui 国外的一款开源react UI组件库
ant-design 由国内蚂蚁金服打造的react UI组件库, 主要用于研发企业级中后台产品,同时提供移动端版本 ant-design-mobile
注意:在使用UI组件库时,尽量采用按需加载的方式打包组件,不要讲UI组件库一次性全盘导入,不利于缩小打包体积。
第5章:react-router
5.1 基本概念
5.1.1 react-router 与 react-router-dom
react-router
是react的一个专门用于实现SPA应用的插件库,基于react的项目基本上都会用到此库,它实现了路由的核心功能。目前我们使用的是react-router5
的版本。react-router-dom
则是基于react-router
并加入了在浏览器运行环境下的一些功能,例如Link
组件、BrowserRouter
和HashRouter
组件。5.1.2 路由分类
路由是一种映射关系,
key
为路由路径,value
为function/component
- 后台路由 服务器路由,
value
是function
,用于处理用户提交的请求并返回响应数据 前台路由 浏览器端路由,
value
是component
,当请求路由path匹配时,界面会更新显示对应的路由,没有发生http请求5.1.3 路由组件分类
- 根组件:
BrowserRouter
(history模式) 和HashRouter
(hash模式)BrowserRouter
(history模式) 和HashRouter
(hash模式)作为路由配置的最外层容器,是两种不同的模式,可根据需要选择。 - 路径匹配组件:
Route
和Switch
导航组件:
Link
和NavLink
5.2 history 库
history
库是react-router
包的唯二依赖,另一个是react本身。这个库管理浏览器会话历史工具库,有三种模式:browser history
, 基于H5 History
Api 封装hash history
基于window.location.hash
封装memory history
在no-DOM环境中适用,比如RN
history
对象提供了和H5 History
Api非常相似的属性和方法length
action
loction
( pathname | search | hash | state )push(path, [state])
replace(path, [state])
go(n)
goBack()
goForward()
block(prompt)
5.3 react-router 相关API
5.3.1 路由组件 BrowserRouter 和 HashRouter
BrowserRouter
(history模式) 和HashRouter
(hash模式)作为路由配置的最外层容器,是两种不同的模式,可根据需要选择。// history 模式: class App extends Component { render() { return ( <BrowserRouter> <Header /> <Route path='/' exact component={Home}></Route> <Route path='/login' exact component={Login}></Route> <Route path='/detail/:id' exact component={Detail}></Route> </BrowserRouter> ) } } // hash 模式: class App extends Component { render() { return ( <HashRouter> <Header /> <Route path='/' exact component={Home}></Route> <Route path='/login' exact component={Login}></Route> <Route path='/detail/:id' exact component={Detail}></Route> </HashRouter> ) } }
5.3.2 路径匹配组件: Route 和 Switch
Route
: 用来控制路径对应显示的组件
参数:path
: 指定跳转路径exact
: 精确匹配路由component
: 路由对应组件render
: 通过写render函数返回具体的dom或组件location
: 与当前历史记录位置以外的位置相匹配,则此功能在路由过渡动效中非常有用sensitive
:是否区分路由大小写strict
: 是否配置路由后面的 '/'Switch
: 渲染与该地址匹配的第一个子节点<Route>
或者<Redirect>
。类似于选项卡,只是匹配到第一个路由后,就不再继续匹配<Switch> <Route path='/home' component={Home}></Route> <Route path='/login' component={Login}></Route> <Route path='/detail' exact component={detail}></Route> <Route path='/detail/:id' component={detailId}></Route> <Redirect to="/home" from='/' /> </Switch>
注意上述的
path='/detail/:id'
,当路由为/detail/1
时,若不加exact
,则只会匹配渲染path='/detail'
路由,添加只会,才会正确地渲染path='/detail/:id'
路由。5.3.3 导航组件: Link 和 NavLink
Link
和NavLink
都可以用来指定路由跳转,NavLink
的可选参数更多。NavLink
可以看做 一个特殊版本的Link
,当它与当前 URL 匹配时,为其渲染元素添加样式属性。<Link to='/login' activeClassName="selected"> <span>登录</span> </Link> <NavLink to="/login" activeStyle={{ fontWeight: 'bold', color: 'red' }} > <span>登录</span> </NavLink>
5.3.4 导航组件: Redirect
<Redirect>
将导航到一个新的地址。即重定向。<Switch> <Route path='/home' exact component={Home}></Route> <Route path='/login' exact component={Login}></Route> <Redirect to="/home" from='/' exact /> </Switch>
上面,当访问路由
/
时,会直接重定向到/home
。<Redirect>
常用语用户登录class Center extends PureComponent { render() { const { loginStatus } = this.props; if (loginStatus) { return ( <div>个人中心</div> ) } else { return <Redirect to='/login' /> } } }
5.3.5 其他
withRouter
函数withRouter
函数可以将一个非路由组件包裹为withRouter
高阶路由组件,使这个非路由组件也能访问到当前路由的match
,location
,history
对象。不论组件何时渲染,withRouter
总会将最新的match
,location
,history
通过props
传递给这个被封装的组件。import React from "react"; import PropTypes from "prop-types"; import { withRouter } from "react-router"; // A simple component that shows the pathname of the current location class ShowTheLocation extends React.Component { static propTypes = { match: PropTypes.object.isRequired, location: PropTypes.object.isRequired, history: PropTypes.object.isRequired }; render() { const { match, location, history } = this.props; return <div>You are now at {location.pathname}</div>; } } // Create a new component that is "connected" (to borrow redux // terminology) to the router. const ShowTheLocationWithRouter = withRouter(ShowTheLocation);
match
对象match
对象包含了<Route>
对当前URL的匹配情况,包含以下属性:params
: 动态路由的key-value
匹配对isExact
: 如果精准匹配上了,没有多余的结尾字符,为truepath
: path匹配表达式,<Route>
时有用url
: url 匹配串,<Link>
时有用// 如果获取match对象 Route component as this.props.match Route render as ({ match }) => () Route children as ({ match }) => () withRouter as this.props.match matchPath as the return value useRouteMatch as the return value
location 对象
{ key: 'ac3df4', // not with HashHistory! pathname: '/somewhere', search: '?some=search-string', hash: '#howdy',
[userDefined]: true,
}
}
```
我们在使用导航组件时,经常用 path 字符串来表述要跳转的地址,但当我们需要传递额外参数时,location对象就可以发挥作用了。如:
```JavaScript
// usually all you need
<Link to="/somewhere"/>
// but you can use a location instead
const location = {
pathname: '/somewhere',
state: { fromDashboard: true }
}
<Link to={location}/>
<Redirect to={location}/>
history.push(location)
history.replace(location)
```
第6章: Redux
6.1 初识Redux
Redux 是专门用于做状态管理的JS库,并不是React的插件库,angular\vue中也可以使用。它可以集中式管理 React 应用的多个组件共享状态。当应用中多个组件需要共享并管理某些状态时,可以用到Redux。比如应用的主题theme管理、音乐类应用的当前歌曲列表管理等。
redux的工作流程如下:
6.2 redux的核心API
6.2.1 createStore()
其作用是创建包含指定 reducer
的 store
对象
import { createStore } from 'redux'
import myReducer from 'mypathtoreducer'
const store = createStore(myReducer)
6.2.2 store对象
store
是 Redux 库最核心的管理对象,它内部维护 state
和 reducer
,提供 getState()
dispatch(action)
subscribe(listener)
三个核心方法用于管理状态
6.2.3 applyMiddleware()
applyMiddleware()
是应用于 Redux 的中间件
import { createStore } from 'redux'
import thunk from 'redux-thunk' // 以异步中间件为例
import myReducer from 'mypathtoreducer'
const store = createStore({
myReducer,
applyMiddleware(thunk) // 应用异步中间件
})
6.2.4 combineReducers()
combineReducers()
的作用是合并多个 reducer
函数
export default combineReducers({
reducerA,
reducerB
...
})
6.3 redux的三个核心概念
6.3.1 action
action
是要执行的行为对象,包含 type
标志属性和行为数据属性,如:
const ADD_POST = 'APP/ADD_POST'
const action = {
type: ADD_POST,
payload: {
post: { content: '111' }
}
}
// 创建 action 的工厂函数会返回一个action
const addPost = (post) => ({
type: ADD_POST,
payload: {
post: { content: '111' }
}
})
6.3.2 reducer
reducer
根据老的 state
和 action
,进行处理,并返回新的 state
export default function addPost(initialState = [], action) {
switch (action.type) {
case ADD_POST:
return [].concat(action.payload.post).concat(initialState)
default:
return initialState
}
}
6.3.3 store
store
是将 state
, action
, reducer
联系到一起的对象
import { createStore } from 'redux'
import myReducer from 'mypathtoreducer'
const store = createStore(myReducer)
store
提供如下api供我们管理状态:
getState()
获取state
dispatch(action)
分发action
, 触发reducer
,即可改版state
subscribe(listener)
注册监听,当产生新的state
时自动调用6.3.4 redux的问题
查看示例后,我们发现:
- Redux 与 React 组件的代码耦合度太高
- 编码不够简洁
6.4 react-redux
6.4.1 初始 react-redux
react-redux 是专门用于简化 react 应用中的 redux 使用情况的一个 react 插件库。其将组建分为两类:
- UI 组件:只负责从 props 中接收数据并进行 UI 的呈现,而不带有任何业务逻辑,不需要使用到 Redux 的API
容器组件: 负责管理数据和业务逻辑,不负责 UI 的呈现,会涉及到 Redux 的相关 API
6.4.2 react-redux 的API
Provider
让所有被包裹的组件都可以得到state数据import { Provider } from 'react-redux' <Provider store={store}> <APP /> </Provider>
connect()
connect() 方法用于包装 UI 组件生成容器组件import { connect } from 'react-redux' export default connect(mapStateToProps, mapDispatchToProps)(Counter)
- mapStateToProps()
将外部的数据(即 state 对象)转换为UI组件的 props 属性,这样在组件内部想要获取 redux 中的 state 时,调用 props 即可 mapDispatchToProps()
将分发 action 的函数转换为 UI 组件的 props 属性,可以在组件内通过调用 props 方法属性的形式分发 actionimport { connect } from 'react-redux' class Counter extends React.Component { componentWillMount () { this.props.onLoad() } render () { let value = this.props.value return (...) } } const mapStateToProps = (state) => ({ value: state.value }) const mapDispatchToProps = (dispatch) => ({ onLoad: addPost }) export default connect(mapStateToProps, mapDispatchToProps)(Counter)
6.4.3 存在的问题
Redux 默认是不能进行一步处理的, 当应用中确实需要在 Redux 中执行异步任务时,可以选择合适的插件来实现,如 saga redux-thunk
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。