React:用于构建用户界面的 JavaScript 库。由 Facebook
开发且开源。
原生 JavaScript 的痛点:
React 的特点:
React Native
中可用 React 语法进行移动端开发相关 JS 库:
react.development.js
:React 核心库react-dom.development.js
:提供 DOM 操作的 React 扩展库babel.min.js
:解析 JSX 语法,转换为 JS 代码
<div id="test">div>
<script type="text/javascript" src="../js/react.development.js">script>
<script type="text/javascript" src="../js/react-dom.development.js">script>
<script type="text/javascript" src="../js/babel.min.js">script>
<script type="text/babel">
//1.创建虚拟DOM
// 不要写引号,因为不是字符串
const VDOM = <h1>Hello,React</h1>
//2.渲染虚拟DOM到页面
// 导入核心库和扩展库后,会有 React 和 ReactDOM 两个对象
ReactDOM.render(VDOM, document.getElementById('test'))
script>
JS创建
<script type="text/javascript">
//1.使用 React 提供的 API 创建虚拟DOM
const VDOM = React.createElement('h1', { id: 'title' }, React.createElement('span', {}, 'Hello,React'))
//2.渲染虚拟DOM到页面
ReactDOM.render(VDOM, document.getElementById('test'))
script>
JSX创建(最终babel转为js)
<script type="text/babel">
//1.创建虚拟DOM
const VDOM = (
<h1 id="title">
<span>Hello,React</span>
</h1>
)
//2.渲染虚拟DOM到页面
ReactDOM.render(VDOM, document.getElementById('test'))
script>
关于虚拟 DOM:
<script >
const VDOM = (
<h1 id="title">
<span>Hello,React</span>
</h1>
)
ReactDOM.render(VDOM, document.getElementById('test'))
const TDOM = document.getElementById('demo')
console.log('虚拟DOM', VDOM)
console.log('真实DOM', [TDOM])
script>
全称:JavaScript XML
React 定义的类似于 XML 的 JS 扩展语法;本质是 React.createElement()
方法的语法糖
XML :
<note> <to>Georgeto> <from>Johnfrom> <heading>Reminderheading> <body>Don't forget the meeting!body> note>
作用:简化创建虚拟 DOM
{}
class
,使用 className
style={ { key: value } }
的形式/
:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>jsx语法规则title>
<style>
.title {
background-color: orange;
width: 200px;
}
style>
head>
<body>
<div id="test">div>
...
<script >
const myId = 'aTgUiGu'
const myData = 'HeLlo,rEaCt'
const VDOM = (
<div>
<h2 className="title" id={myId.toLowerCase()}>
<span style={{ color: 'white', fontSize: '19px' }}>{myData.toLowerCase()}</span>
</h2>
<input type="text" />
// very good
//
</div>
)
ReactDOM.render(VDOM, document.getElementById('test'))
script>
body>
html>
注意区分:JS 语句(代码) 与 JS 表达式:
a
a + b
demo(1)
arr.map()
function test() {}
2. 语句(代码):
if(){}
for(){}
switch(){case:xxxx}
3.循环遍历数组
<script >
let list = ['Angular', 'React', 'Vue']
const VDOM = (
<div>
<h1>前端js框架列表</h1>
<ul>
// React 会自动遍历数组
{list.map((item, index) => {
// Each child in a list should have a unique "key" prop.
return <li key={index}>{item}</li>
})}
</ul>
</div>
) ReactDOM.render(VDOM, document.getElementById('test'))
script>
<script >
//1.创建函数式组件
function MyComponent() {
//此处的 this 是 undefined,因为 babel 编译后开启了严格模式
console.log(this)
return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
}
//2.渲染组件到页面
ReactDOM.render(<MyComponent />, document.getElementById('test'))
/*
渲染组件的过程:
- React 解析标签,寻找对应组件
- 发现组件是函数式组件,则调用函数,将返回的虚拟 DOM 转换为真实 DOM ,并渲染到页面中
*/
script>
要点:
渲染组件的过程:
<script>
// 创建类式组件
// 不同点 要class 类并且继承React.Component
class MyComponent extends React.Component {
render() {
console.log('render中的this:', this) // this 指向实例对象
return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
}
}
ReactDOM.render(<MyComponent />, document.getElementById('test'))
script>
组件渲染过程:
new
该类的实例对象,通过实例调用原型上的 render
方法render
返回的虚拟 DOM 转为真实 DOM ,渲染到页面上state
是组件实例对象最重要的属性,值为对象。又称为状态机,通过更新组件的 state
来更新对应的页面显示。
要点:
state
this
指向问题setState
修改 state
状态constructor
、render
、自定义方法的调用次数<script >
class Weather extends React.Component {
// 调用一次
constructor(props) {
super(props)
// 初始化 state
this.state = { isHot: true, wind: '微风' }
// 解决 this 指向问题 不加这个的话,函数是开启严格模式下的直接调用this会输出undefined
this.changeWeather = this.changeWeather.bind(this)
}
// 调用 1+N 次
render() {
// 读取状态
const { isHot } = this.state
// 事件绑定
return <h1 onClick={this.changeWeather}>今天天气 {isHot ? '炎热' : '凉爽'}</h1>
/* onClick={this.changeWeather} 不加this的话调用不到函数
等于play(){
这样直接调用调用不到原型上的study一样,得加this才能调用到
study()
}
*/
}
// 点一次调一次
changeWeather() {
const isHot = this.state.isHot
// 对 state 的修改是一种合并而非替换,即 wind 依然存在
this.setState({ isHot: !isHot })
}
}
ReactDOM.render(<Weather />, document.getElementById('test'))
script>
简化版:
<script>
class Weather extends React.Component {
state = { isHot: true, wind: '微风' }
render() {
const { isHot } = this.state
return <h2 onClick={this.changeWeather}>天气{isHot ? '炎热' : '凉爽'}</h2>
}
// 采用箭头函数 + 赋值语句形式
changeWeather = () => {
const isHot = this.state.isHot
this.setState = { isHot: !isHot }
}
}
ReactDOM.render(<Weather />, document.getElementById('test'))
script>
每个组件对象都有 props
属性,组件标签的属性都保存在 props
中。
props
是只读的,不能修改。
<script>
class Person extends React.Component {
render() {
const { name, age, sex } = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
}
// 类似于标签属性传值
ReactDOM.render(<Person name="Lily" age={19} sex="男" />, document.getElementById('test'))
script>
<script >
class Person extends React.Component {
render() {
const { name, age, sex } = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
}
const obj = { name: 'Ben', age: 21, sex: '女' }
ReactDOM.render(<Person {...obj} />, document.getElementById('test'))
script>
在 React 15.5
以前,React
身上有一个 PropTypes
属性可直接使用,即 name: React.PropTypes.string.isRequired
,没有把 PropTypes
单独封装为一个模块。
从 React 15.5
开始,把 PropTypes
单独封装为一个模块,需要额外导入使用。
<script type="text/javascript" src="../js/prop-types.js">script>
<script >
class Person extends React.Component {
render() {
const { name, age, sex } = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
}
// 类型和必要性限制
// 注意Person.propTypes 和 PropTypes
Person.propTypes = {
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number,
// 限制 speak 为函数
speak: PropTypes.func,
}
// 指定默认值
Person.defaultProps = {
sex: 'male',
age: 19,
}
ReactDOM.render(<Person name="Vue" sex="male" age={11} speak={speak} />, document.getElementById('test'))
function speak() {
console.log('speaking...')
}
script>
Person.propTypes
和 Person.defaultProps
可以看作在类身上添加属性,利用 static
关键词就能在类内部进行声明。因此所谓简写只是从类外部移到类内部。
<script type="text/javascript" src="../js/prop-types.js">script>
<script >
class Person extends React.Component {
static propTypes = {
// 必修为字符串且必填
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number,
// 限制 speak 为函数
speak: PropTypes.func,
}
// static 是添加到Person上的属性
static defaultProps = {
sex: 'male',
age: 19,
}
render() {
const { name, age, sex } = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
}
ReactDOM.render(<Person name="Vue" sex="male" age={11} speak={speak} />, document.getElementById('test'))
function speak() {
console.log('speaking...')
}
script>
官网文档说明(opens new window)
构造函数一般用在两种情况:
this.state
赋值对象来初始化内部 state
constructor(props) {
super(props)
// 初始化 state
this.state = { isHot: true, wind: '微风' }
// 解决 this 指向问题
this.changeWeather = this.changeWeather.bind(this)
}
因此构造器一般都不需要写。如果要在构造器内使用 this.props
才声明构造器,并且需要在最开始调用 super(props)
:
否则this.props 就会变为undefined ,不过一般也可以调用props
constructor(props) {
super(props)
console.log(this.props)
}
由于函数可以传递参数,因此函数式组件可以使用 props
。
<script type="text/javascript" src="../js/prop-types.js">script>
<script>
function Person(props) {
const { name, age, sex } = props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
// 限制就必须写在函数外面了,因为函数里面this丢失(开启了严格模式)
Person.propTypes = {
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number,
}
Person.defaultProps = {
sex: '男',
age: 18,
}
ReactDOM.render(<Person name="jerry" />, document.getElementById('test'))
script>
通过定义 ref
属性可以给标签添加标识。即获取该节点
这种形式已过时,效率不高,官方 (opens new window)不建议使用。
<script>
class Demo extends React.Component {
showData = () => {
const { input1 } = this.refs
alert(input1.value)
}
render() {
return (
<div>
<input ref="input1" type="text" placeholder="点击按钮提示数据" />
<button onClick={this.showData}>点我提示左侧的数据</button>
</div>
)
}
}
ReactDOM.render(<Demo />, document.getElementById('test'))
script>
要点:
c => this.input1 = c
就是给组件实例添加 input1
属性,值为节点(currentNode)this
是 render
函数里的 this
,即组件实例<script>
class Demo extends React.Component {
showData = () => {
// 直接从this 里取出input1 属性
const { input1 } = this
alert(input1.value)
}
render() {
return (
<div>
<input ref={(c) => {this.input1 = c }}type="text" placeholder="点击按钮提示数据" />
<button onClick={this.showData}>点我提示左侧的数据</button>
</div>
)
}
}
ReactDOM.render(<Demo />, document.getElementById('test'))
script>
关于回调 ref
执行次数的问题,官网 (opens new window)描述:
TIP
如果
ref
回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数null
,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的ref
并且设置新的。通过将ref
的回调函数定义成class
的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。
即内联函数形式,在更新过程重新调用render中 ref
回调会被执行两次,第一次传入 null
,第二次传入 DOM 元素。若是下述形式,则只执行一次。但是对功能实现没有影响,因此一般也是用内联函数形式。
函数定义成 class
的绑定函数的方式
<script>
//创建组件
class Demo extends React.Component {
state = { isHot: false }
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={this.saveInput} type="text" />
</div>
)
}
}
ReactDOM.render(<Demo />, document.getElementById('test'))
script>
该方式通过调用 React.createRef
返回一个容器用于存储节点,且一个容器只能存储一个节点。(官方推荐的用法)
<script>
class Demo extends React.Component {
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 />, document.getElementById('test'))
script>
onClick、onBlur
:为了更好的兼容性event.target
可获取触发事件的 DOM 元素:勿过度使用 ref
当触发事件的元素和需要操作的元素为同一个时,可以不使用 ref
:
class Demo extends React.Component {
showData2 = (event) => {
alert(event.target.value)
}
render() {
return (
<div>
<input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />
</div>
)
}
}
包含表单的组件分类:
尽量使用受控组件,因为非受控组件需要使用大量的 ref
。
// 非受控组件
class Login extends React.Component {
handleSubmit = (event) => {
// 阻止表单提交的默认事件
event.preventDefault()
const { username, password } = this
alert(`用户名是:${username.value}, 密码是:${password.value}`)
}
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名:
<input ref={(c) => (this.username = c)} type="text" name="username" />
密码:
<input ref={(c) => (this.password = c)} type="password" name="password" />
<button>登录</button>
</form>
)
}
}
// 受控组件
class Login extends React.Component {
state = {
username: '',
password: '',
}
saveUsername = (event) => {
this.setState({ username: event.target.value })
}
savePassword = (event) => {
this.setState({ password: event.target.value })
}
handleSubmit = (event) => {
event.preventDefault()
const { username, password } = this.state
alert(`用户名是:${username}, 密码是:${password}`)
}
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名:
<input onChange={this.saveUsername} type="text" name="username" />
密码:// onChange input改变一次触发一次
<input onChange={this.savePassword} type="password" name="password" />
<button>登录</button>
</form>
)
}
}
对上述受控组件的代码进行优化,希望把 saveUsername
和 savePassword
合并为一个函数。
要点:
Promise、setTimeout、Array.map()
// 函数柯里化
function sum(a) {
return (b) => {
return (c) => {
return a + b + c
}
}
}
// 使用高阶函数和柯里化写法
class Login extends React.Component {
state = {
username: '',
password: '',
}
saveFormData = (dataType) => {
return (event) => {
// 对象里传入属性
this.setState({ [dataType]: event.target.value })
}
}
handleSubmit = (event) => {
event.preventDefault()
const { username, password } = this.state
alert(`用户名是:${username}, 密码是:${password}`)
}
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名:
<input onChange={this.saveFormData('username')} type="text" name="username" />
密码:
<input onChange={this.saveFormData('password')} type="password" name="password" />
<button>登录</button>
</form>
)
}
}
/* 不使用柯里化写法
onChange={(event) => this.saveFormData('username', event)} 主要改变在这既可以接受传入的Str和事件获取里面的值
*/
class Login extends React.Component {
state = {
username: '',
password: '',
}
// 简化不用高阶函数
saveFormData = (dataType, event) => {
this.setState({ [dataType]: event.target.value })
}
handleSubmit = (event) => {
event.preventDefault()
const { username, password } = this.state
alert(`用户名是:${username}, 密码是:${password}`)
}
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名:
<input onChange={(event) => this.saveFormData('username', event)} type="text" name="username" />
密码:
<input onChange={(event) => this.saveFormData('password', event)} type="password" name="password" />
<button>登录</button>
</form>
)
}
}
初始化阶段:ReactDOM.render()
触发的初次渲染
constructor
componentWillMount
(组件将要挂载时调用)render
componentDidMount
(组件挂载完毕调用)更新阶段
render
触发的更新componentWillReceiveProps
( 父组件更新数据传值时调用,并不是一开始就会调用可以在里面接受到props值)
shouldComponentUpdate
:控制组件是否更新的阀门,返回值为布尔值,默认为 true
。若返回 false
,则后续流程不会进行。
componentWillUpdate
render
componentDidUpdate
(组件完成数据更新)
2.组件内部调用 this.setState()
修改状态
shouldComponentUpdate
( 阀门,必修return 一个布尔值,true 就往下执行,false 就不执行即不更新页面 )
componentWillUpdate
render
componentDidUpdate
3.组件内部调用 this.forceUpdate()
强制更新 (数据不更新页面也重新渲染时调用)
componentWillUpdate
render
componentDidUpdate
卸载阶段:ReactDOM.unmountComponentAtNode()
触发
componentWillUnmount
更改内容 (opens new window):
componentWillMount
、componentWillReceiveProps
、 componentWillUpdate
。在新版本中这三个钩子需要加 UNSAFE_
前缀才能使用,后续可能会废弃。getDerivedStateFromProps
、getSnapshotBeforeUpdate
static getDerivedStateFromProps(props, state) (opens new window):
static
修饰state
或返回 null
state
的值任何时候都取决于 props
getSnapshotBeforeUpdate(prevProps, prevState) (opens new window):
componentDidUpdate()
class {
static getDerivedStateFromProps(props,state){
console.log('getDerivedStateFromProps',props,state);
return null
}
getSnapshotBeforeUpdate(){
console.log('getSnapshotBeforeUpdate');
return 'atguigu'
}
componentDidUpdate(preProps,preState,snapshotValue){
console.log('componentDidUpdate',preProps,preState,snapshotValue);
}
}
// getSnapshotBeforeUpdate 案例
class NewsList extends React.Component {
state = { newsArr: [] }
componentDidMount() {
setInterval(() => {
//获取原状态
const { newsArr } = this.state
//模拟一条新闻
const news = '新闻' + (newsArr.length + 1)
//更新状态
this.setState({ newsArr: [news, ...newsArr] })
}, 1000)
}
getSnapshotBeforeUpdate() {
return this.refs.list.scrollHeight
}
componentDidUpdate(preProps, preState, height) {
// 每次都 往上滚 对应添加的节点距离
this.refs.list.scrollTop += this.refs.list.scrollHeight - height
}
render() {
return (
<div className="list" ref="list">
{this.state.newsArr.map((n, index) => {
return (
<div key={index} className="news">
{n}
</div>
)
})}
</div>
)
}
}
ReactDOM.render(<NewsList />, document.getElementById('test'))
render
:初始化渲染和更新渲染componentDidMount
:进行初始化,如开启定时器、发送网络请求、订阅消息componentWillUnmount
:进行收尾,如关闭定时器、取消订阅消息key
的作用:
key
是虚拟 DOM 对象的标识,可提高页面更新渲染的效率。
当状态中的数据发生变化时,React 会根据新数据生成新的虚拟 DOM ,接着对新旧虚拟 DOM 进行 Diff 比较,规则如下:
使用 index
作为 key
可能引发的问题:
// 使用 index 作为 key 引发的问题
class Person extends React.Component {
state = {
persons: [
{ id: 1, name: '小张', age: 18 },
{ id: 2, name: '小李', age: 19 },
],
}
add = () => {
const { persons } = this.state
const p = { id: persons.length + 1, name: '小王', age: 20 }
this.setState({ persons: [p, ...persons] })
}
render() {
return (
<div>
<h2>展示人员信息</h2>
<button onClick={this.add}>添加小王</button>
<h3>使用index作为key</h3>
<ul>
{this.state.persons.map((personObj, index) => {
return (
// 输入类的 DOM(如 input 输入框) ,则会产生错误的 DOM 更新。并且渲染效率低
<li key={index}>
{personObj.name}---{personObj.age}
<input type="text" />
</li>
)
})}
</div>
)
}
}
npm i -g create-react-app
create-react-app 项目名称
cd 项目名称
npm start
public
:静态资源文件
manifest.json
:应用加壳(把网页变成安卓/IOS 软件)的配置文件robots.txt
:爬虫协议文件src
:源码文件
App.test.js
:用于给 App
组件做测试,一般不用index.js
:入口文件reportWebVitals.js
:页面性能分析文件,需要 web-vitals
库支持setupTests.js
:组件单元测试文件,需要 jest-dom
库支持index.html
代码分析:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="red" />
<meta name="description" content="Web site created using create-react-app" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>React Apptitle>
head>
<body>
<noscript>You need to enable JavaScript to run this app.noscript>
<div id="root">div>
body>
html>
样式的模块化可用于解决样式冲突的问题。该方法比较麻烦,实际开发用的比较少。用 less
就能解决了。
component/Hello
文件下的 index.css
改名为 index.module.css
。
.title {
background-color: orange;
}
Hello
组件导入样式:
import { Component } from 'react'
import hello from './index.module.css'
export default class Hello extends Component {
render() {
return Hello,React!
}
}
className
、style
的写法state
中?state
中state
中,即状态提升父传子:直接通过 props
传递
子传父:父组件通过 props
给子组件传递一个函数,子组件调用该函数
//子组件
this.props.XXX(函数)
//父组件
<子组件 xxx(函数名)={对应的处理函数}/>
写成这种index形式,引入时只需要引入对应文件夹,react脚手架会自动找到里面的index.js / jsx
导入时 .js 跟 .jsx 后缀可以省略
// 父组件
class Father extends Component {
state: {
todos: [{ id: '001', name: '吃饭', done: true }],
flag: true,
}
addTodo = (todo) => {
const { todos } = this.state
const newTodos = [todo, ...todos]
this.setState({ todos: newTodos })
}
render() {
return <List todos={this.state.todos} addTodo={this.addTodo} />
}
}
// 子组件
class Son extends Component {
// 由于 addTodo 是箭头函数,this 指向父组件实例对象,因此子组件调用它相当于父组件实例在调用
handleClick = () => {
this.props.addTodo({ id: '002', name: '敲代码', done: false })
}
render() {
return <button onClick={this.handleClick}>添加</button>
}
}
// 键盘按起事件
onKeyUp = (event)=>{
const {target,keyCode} = event
// 判断用户是否敲下回车
if(keyCode != 13) return
// 判断用户输入的是否为空
if(target.value.trim() == '') {
alert("不能输入空")
return
}
// 敲下回车将值传去父组件
this.props.addList(target.value)
// 随后清空input值
target.value = ''
}
// 弹出对话框询问,需要添加window
if(window.confirm('是否删除'))
defaultChecked
和 checked
的区别,类似的还有:defaultValue
和 value
方法一:
在 package.json
文件中进行配置:
"proxy": "http://localhost:5000"
方法二:
在 src
目录下创建代理配置文件 setupProxy.js
,进行配置:
const proxy = require('http-proxy-middleware')
module.exports = function (app) {
app.use(
//api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
proxy('/api1', {
//配置转发目标地址(能返回数据的服务器地址)
target: 'http://localhost:5000',
//控制服务器接收到的请求头中host字段的值
/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
changeOrigin默认值为false,但一般将changeOrigin改为true
*/
changeOrigin: true,
//去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
pathRewrite: { '^/api1': '' },
}),
proxy('/api2', {
target: 'http://localhost:5001',
changeOrigin: true,
pathRewrite: { '^/api2': '' },
})
)
}
即 React 中兄弟组件或任意组件之间的通信方式。
使用的工具库:PubSubJS(opens new window)
下载安装 PubSubJS
:npm install pubsub-js --save
基础用法:
import PubSub from 'pubsub-js'
// 订阅消息
var token = PubSub.subscribe('topic', (msg, data) => {
console.log(msg, data)
// msg 是订阅的名称,data才是发送的数据
})
// 发布消息
PubSub.publish('topic', 'hello react')
// 取消订阅
PubSub.unsubscribe(token)
import axios from 'axios'
axios.get('url').then((res,err)=>{即可获得数据})// 只需在componentDidMount 掉用即可
let obj = { a: { b: 1 } }
//传统解构赋值
const { a } = obj
//连续解构赋值
const {
a: { b },
} = obj
//连续解构赋值 + 重命名
const {
a: { b: value },
} = obj
componentWillUnmount
钩子中取消订阅fetch
发送请求(关注分离的设计思想)try {
// 先看服务器是否联系得上
const response = await fetch(`/api1/search/users2?q=${keyWord}`)
// 再获取数据
const data = await response.json()
console.log(data)
} catch (error) {
console.log('请求出错', error)
}
何为路由?
key
为路径,value
可能是 function
或 组件后端路由:
value
是 function
,用于处理客户端的请求router.get(path, function(req, res))
前端路由:
value
是组件
/test
,展示 Test
组件安装 react-router-dom
:
// 安装 5.X 版本路由
npm install [email protected] -S
// 最新已经 6.X 版本,用法和 5.X 有所不同
npm install react-router-dom -S
<Link to='/about'>about</Link>
<Routes>
<Route path="/about" element={<About />} />
</Routes>
的最外侧包裹
或
:import { BrowserRouter } from 'react-router-dom'
import App from './App'
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
)
6.x
版本的用法参考文章(opens new window)
router v6 与router v5的区别
router v6的快速上手
以 5.x
版本为例展示基本使用:
// App.jsx
import React, { Component } from 'react'
import { Link, Route } from 'react-router-dom'
import Home from './components/Home'
import About from './components/About'
export default class App extends Component {
render() {
return (
<div>
<div className="list-group">
<Link className="list-group-item" to="/about">
About
</Link>
<Link className="list-group-item" to="/home">
Home
</Link>
</div>
<div className="panel-body">
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
</div>
</div>
)
}
}
的最外侧包裹
或
:
// index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import App from './App'
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
)
6.x 版本的用法
// App.jsx
import React, { Component } from 'react'
import { Routes,Route,Link } from 'react-router-dom';
import Home from './components/Home'
import About from './components/About'
export default class App extends Component {
render() {
return (
<div>
// 控制路由的转换
<Link className="list-group-item active" to='/home'>home</Link>
<Link className="list-group-item" to='/about'>about</Link>
// 展示对应路由的内容
// 与5.x 不同于 需要用Routes 包裹并更换component 为 element
<Routes>
<Route path="/home" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</div>
)
}
}
的最外侧包裹
或
:
import React from "react";
import ReactDOM from 'react-dom'
import { BrowserRouter as Router } from 'react-router-dom';
import App from './App'
ReactDOM.render(
<Router>
<App/>
</Router>,document.getElementById('root'))
写法不同:
存放位置不同:
components
pages
接收到的 props
不同:
// 从this.props 里接受到的三个属性的主要参数
history:
go: ƒ go(n)
goBack: ƒ goBack()
goForward: ƒ goForward()
push: ƒ push(path, state)
replace: ƒ replace(path, state)
location:
pathname: "/home/message/detail/2/hello"
search: ""
state: undefined
match:
params: {}
path: "/home/message/detail/:id/:title"
url: "/home/message/detail/2/hello"
NavLink
可以实现路由链接的高亮,通过 activeClassName
指定样式名,默认追加类名为 active
。
<NavLink activeClassName="demo" to="/about">AboutNavLink>
<NavLink activeClassName="demo" to="/home">HomeNavLink>
封装 NavLink
组件:由于 NavLink
组件中重复的代码太多,因此进行二次封装。
※ 细节点:组件标签的内容会传递到 this.props.children
属性中,反过来通过指定标签的 children
属性可以修改组件标签内容
// MyNavLink 组件
import React, { Component } from 'react'
import { NavLink } from 'react-router-dom'
export default class MyNavLink extends Component {
render() {
// this.props.children 可以取到标签内容,如 About, Home
// 反过来通过指定标签的 children 属性可以修改标签内容
return <NavLink activeClassName="demo" className="list-group-item" {...this.props} />
}
}
封装完后使用
<MyNavLink to="/about">AboutMyNavLink>
<MyNavLink to="/home">HomeMyNavLink>
Switch
可以提高路由匹配效率,如果匹配成功,则不再继续匹配后面的路由,即单一匹配。
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Route path="/home" component={Test} />
Switch>
6.x 中switch 已更改用法(为Routes)
<Routes>
<Route path="/home" element={ /> } />
<Route path="/about" element={ /> } />
Routes>
public/index.html
中 引入样式时不写 ./
写 /
(常用)public/index.html
中 引入样式时不写 ./
写 %PUBLIC_URL%
(常用)HashRouter
<link rel="stylesheet" href="/css/bootstrap.css" />
<link rel="stylesheet" href="%PUBLIC_URL%/css/bootstrap.css" />
import {Redirect} from 'react-router-dom'
<Switch>
<Route path="/about" component="{About}" />
<Route path="/home" component="{Home}" />
// 放在最后面
<Redirect to="/about" />
Switch>
6.x 中已移除Redirect
import { Routes,Route,Navigate } from 'react-router-dom';
<Routes>
<Route path="/" element={ replace to="/home" /> } />
Routes>
path
<MyNavLink to="/about">AboutMyNavLink>
<MyNavLink to="/home">HomeMyNavLink>
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Redirect to="/about" />
Switch>
<ul className="nav nav-tabs">
<li><MyNavLink to="/home/news">NewsMyNavLink> li>
<li><MyNavLink to="/home/message">MessageMyNavLink>li>
ul>
<Switch>
<Route path="/home/news" component={News} />
<Route path="/home/message" component={Message} />
<Redirect to="/home/news" />
Switch>
三种方式:params, search, state
参数
三种方式对比:
state
方式当前页面刷新可保留参数,但在新页面打开不能保留。前两种方式由于参数保存在 URL 地址上,因此都能保留参数。params
和 search
参数都会变成字符串
<Link to={`/home/mess/details/${item.id}/${item.name}`}>{item.name}Link>
<Route path='/home/mess/details/:id/:title' component={Details}>Route>
//接收参数
const { id, title } = this.props.match.params
{item.name}Link>
<Route path='/home/mess/details' component={Details}>Route>
//接收参数
// 解析 urlenencode的函数
const app = (str)=>{
const search = this.props.location.search
var query = search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if(pair[0] == str){return pair[1];}
}
return (false)
}
const id = app('id') // 得到id 的value
const title = app('title') // 得到 title 的value
// 可以获得对应id 的item对象
const findRes = data.find(item=>{
return item.id == id
})
// 或者引入 import qs from 'querystring'
import qs from 'querystring' // qs.parse 可以转urlencoded 为对象形式 qs.stringfy() 可以转为urlencoded形式
const { search } = this.props.location
const { id, title } = qs.parse(search.slice(1))
{item.name}Link>
<Route path='/home/mess/details' component={Details}>Route>
//state接收参数
// 接受state 参数
const {id,title} = this.props.location.state
编程式导航是使用路由组件 this.props.history
提供的 API 进行路由跳转:(只有路由组件才有该方法)
this.props.history.push(path, state)
this.props.history.replace(path, state)
this.props.history.goForward()
this.props.history.goBack()
this.props.history.go(n)
// 调用 路由组件 porps 的方法
pushShow(id,title){
this.props.history.push(`/home/mess/details/${id}/${title}`)
}
// 绑定 编程式导航的方法
<button onClick={()=>this.pushShow(item.id,item.name)}>push</button>
// 编程式导航传参
this.props.history.push(`/home/message/detail/${id}/${title}`)
this.props.history.push(`/home/message/detail?id=${id}&title=${title}`)
this.props.history.push(`/home/message/detail`, { id: id, title: title })
withRouter
的作用:加工一般组件,让其拥有路由组件的 API ,如 this.props.history.push
等。
import React, {Component} from 'react'
import {withRouter} from 'react-router-dom'
class Header extends Component {
...
}
// 导出时用withRouter加工后的组件
export default withRouter(Header)
底层原理不一样:
BrowserRouter
使用的是 H5 的 history API,不兼容 IE9 及以下版本。HashRouter
使用的是 URL 的哈希值。路径表现形式不一样
BrowserRouter
的路径中没有 #
,如:localhost:3000/demo/test
HashRouter
的路径包含#,如:localhost:3000/#/demo/test
刷新后对路由 state
参数的影响
BrowserRouter
没有影响,因为 state
保存在 history
对象中。HashRouter
刷新后会导致路由 state
参数的丢失!备注:HashRouter
可以用于解决一些路径错误相关的问题。
以下配置是
3.x
版本,4.x
版本见官网(opens new window)
1、安装依赖:
npm install react-app-rewired customize-cra babel-plugin-import // 处理按需导入
npm install less less-loader // 自定义主题文件
2、修改 package.json
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
}
3、根目录下创建 config-overrides.js
//配置具体的修改规则
const { override, fixBabelImports, addLessLoader } = require('customize-cra')
module.exports = override(
fixBabelImports('import', { // 组件按需导入无需再导入整个包
libraryName: 'antd',
libraryDirectory: 'es',
style: true,
}),
addLessLoader({
lessOptions: { // 从less5.x以上需要套上lessOptions才能配置文件
javascriptEnabled: true,
modifyVars: { '@primary-color': 'green' },// 自定义全局主题颜色为绿色
},
})
)
官网(opens new window)
中文文档(opens new window)
Redux 为何物
何时用 Redux
Redux 工作流程
Action Creators
Action Creators
创建 action
:同步 action
是一个普通对象,异步 action
是一个函数Store
调用 dispatch()
分发 action
给 Reducers
执行Reducers
接收 previousState
、action
两个参数,对状态进行加工后返回新状态Store
调用 getState()
把状态传给组件// store.js
// 导入创建store方法,跟处理action异步函数的方法
import {createStore,applyMiddleware} from 'redux'
// 导入需要管理的组件
import count from './count_reducer'
// 引入处理异步函数的中间件
import thunk from 'redux-thunk'
// 导出 store 第一个参数为需要管理的组件,第二个参数为中间件
export default createStore(count,applyMiddleware(thunk))
// 赋初始值时为0
const InitState = 0
// 第一次action里的 type 为 undefined ,avalue 为空 调用函数进行初始化状态 返回初始状态为0
// 向外暴露一个方法
export default function count (preState=InitState,action){
// 从action 里解构赋值出要用的type跟state
const {type,state} = action
switch (type) {
case 'increment': // 加法
return preState + state*1
case 'decrement': // 减法
return preState - state*1
default:
return preState
}
}
// 用来给count 创建action对象
// 返回的是一个对象
export const actionIncrement = (state)=>({type:'increment',state})
export const actionDecrement = (state)=>({type:'decrement',state})
// 创建异步action对象
export const IncrementSync = (state,time)=>{
// 异步api 不同在于返回一个函数给store 然后会传一个dispatch参数
return (dispatch)=>{
setTimeout(() => {
dispatch(actionIncrement(state))
}, time);
}
}
import React, { Component } from 'react'
// 引入stroe
import store from './store/store'
// 引入action创建对象
import {actionIncrement,actionDecrement,IncrementSync} from './store/count_action'
export default class App extends Component {
// 监听store里的属性变化后重新调用render渲染画面
componentDidMount(){
store.subscribe(()=>{
this.setState({})
})
}
// 加+1 的处理函数
AddNum = ()=>{
const {value} = this.allNum
// const {count} = this.state
// this.setState({count:count+value*1}) 原生写法
// store.dispatch({type:'increment',state:value}) 简写版不用action 直接dispatch去调用reducer
// 用action 完整版写法
store.dispatch(actionIncrement(value))
}
AddNumSync = ()=>{
const {value} = this.allNum
// const {count} = this.state
// setTimeout(() => {
// store.dispatch({type:'increment',state:value})
// 调用action异步方法
store.dispatch(IncrementSync(value,500))
// }, 500);
}
render() {
return (
<div>
// store.getState() 获取到当前的count值
<h1>当前求和数:{store.getState()}</h1>
<select ref = {c => this.allNum = c}>
<option value='1'>1</option>
<option value='2'>2</option>
<option value='3'>3</option>
</select>
<button onClick={this.AddNum}>+</button>
<button onClick={this.AddNumSync}>异步加</button>
</div>
)
}
}
action
:
type
:标识属性,值为字符串,唯一,必须属性data
:数据属性,类型任意,可选属性{type: 'increment', data: 2}
reducer
:
action
产生新状态纯函数:输入同样的实参,必定得到同样的输出
- 不能改写参数数据
- 不产生副作用,如网络请求、输入输出设备(网络请求不稳定)
- 不能调用
Date.now()
、Math.random()
等不纯方法
store
:
state
和 reducer
store.getState()
:获取状态store.dispatch(action)
:分发任务,触发 reducer
调用,产生新状态store.subscribe(func)
:注册监听函数,当状态改变自动调用作用:合并多个reducer函数
//代码示例 ------------------ redux/reducers/index.js ------------------------------------ /** * 该文件用于汇总所有的reducer为一个总的reducer */ //引入combineReducers,用于汇总多个reducer import {combineReducers} from 'redux' //引入为Count组件服务的reducer import count from './count' import persons from './person' //汇总所有的reducer变为一个总的reducer export default combineReducers({ count,persons })
index.js
中统一监听状态变化,也可以在组件中单独监听。注意不能直接 this.render()
调用 render
函数,要通过 this.setState({})
间接调用reducer
由 store
自动触发首次调用,传递的 preState
为 undefined
,action
为 {type: '@@REDUX/ININT_a.5.v.9'}
类似的东东,只有 type
安装异步中间件:
npm install redux-thunk -S
要点:
action
action
action
的函数返回一个函数,该函数中写异步任务action
操作状态action
不是必要的,完全可以在组件中等待异步任务结果返回在分发同步 action
// store.js
/**
* 该文件撰文用于暴露一个store对象,整个应用只有一个store对象
*/
//引入createStore,专门用于创建redux中最为核心的store对象
import {createStore,applyMiddleware} from 'redux'
//引入汇总后的reducer
import reducer from './reducers'
//引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
//引入redux-devtools-extension
import {composeWithDevTools} from 'redux-devtools-extension'
//暴露store
export default createStore(reducer,composeWithDevTools(applyMiddleware(thunk)))
// count_action.js
import { INCREMENT, DECREMENT } from './constant.js'
export const createIncrementAction = (data) => ({ type: INCREMENT, data })
export const createDecrementAction = (data) => ({ type: DECREMENT, data })
// 异步 action 返回一个函数
export const createIncrementAsyncAction = (data, time) => {
return (dispatch) => {
setTimeout(() => {
dispatch(createIncrementAction(data))
}, time)
}
}
// Count.jsx
incrementAsync = () => {
const { value } = this.selectNumber
store.dispatch(createIncrementAsyncAction(value * 1))
}
整个过程简单理解:store
在分发 action
时,发现返回一个函数,那它知道这是个异步 action
。因此 store
勉为其难地帮忙执行这个函数,同时给这个函数传递 dispatch
方法,等待异步任务完成取到数据后,直接调用 dispatch
方法分发同步 action
。
React-Redux 是一个插件库,用于简化 React 中使用 Redux 。
React-Redux 将组件分为两类:
props
接收数据components
文件夹下containers
文件夹下作用: 让所有组件都可以得到state数据
import React from 'react' import ReactDOM from "react-dom" import App from './App' import store from './redux/store' import {Provider} from 'react-redux' ReactDOM.render( /* 此处需要用Provider包裹App,目的是让App所有的后代容器组件都能接收到store */ <Provider store={store}> <App/> </Provider>, document.getElementById('root') )
connect()()
- 作用: 用于包装 UI 组件生成容器组件
- 使用connect(
mapDispatchToProps
,mapDispatchToProps
)(UI组件)注意点:
- 该方法默认传入
state
与dispatch
- 可以省略
dispatch
直接传入action
方法,该api会自动帮你调用dispatch
作用:将外部的数据(即
state对象
)转换为UI组件的标签属性1.mapStateToProps函数返回的是一个对象;
2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
3.mapStateToProps
用于传递状态
function mapStateToProps(state){ return {count:state} }
作用:将
分发action的函数
转换为UI组件的标签属性
- mapDispatchToProps函数返回的是一个对象;
- 返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
- mapDispatchToProps
用于传递操作状态的方法
- 可以省略
dispatch
,直接传入action
,api将会自动调用
dispatch
------------------------------不简化代码----------------------------------------------- /* 1.mapStateToProps函数返回的是一个对象; 2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value 3.mapStateToProps用于传递状态 */ function mapStateToProps(state){ return {count:state} } /* 1.mapDispatchToProps函数返回的是一个对象; 2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value 3.mapDispatchToProps用于传递操作状态的方法 */ function mapDispatchToProps(dispatch){ return { jia:number => dispatch(createIncrementAction(number)), jian:number => dispatch(createDecrementAction(number)), jiaAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)), } } //使用connect()()创建并暴露一个Count的容器组件 export default connect(mapStateToProps,mapDispatchToProps)(CountUI) ----------------下面是简化代码----------------------------- //使用connect()()创建并暴露一个Count的容器组件 //使用connect(传入状态,操作状态方法)(UI组件) export default connect( state => ({ count: state.count, personCount: state.persons.length }), {increment, decrement, incrementAsync} )(Count)
首先规范化文件结构,容器组件和 UI 组件合为一体后放在 containers
文件夹。redux
文件夹新建 actions
和 reducers
文件夹分别用于存放每个组件对应的 action
和 reducer
。
新建 Person
组件对应的 action
和 reducer
:
--------------------------------------person.js-------------------------------
import {ADD_PERSON} from '../constant'
//创建增加一个人的action动作对象
export const addPerson=personObj=>({
type:ADD_PERSON,
data:personObj
})
--------------------------------------person.js-------------------------------
import {ADD_PERSON} from '../constant'
//初始化人的列表
const initState = [{id:'001',name:'tom',age:18}]
export default function personReducer(preState=initState,action){
// console.log('personReducer@#@#@#');
const {type,data} = action
switch (type) {
case ADD_PERSON: //若是添加一个人
//preState.unshift(data) //此处不可以这样写,这样会导致preState被改写了,personReducer就不是纯函数了。
return [data,...preState]
default:
return preState
}
}
关键步骤:在 store.js
中使用 combineReducers()
整合多个 reducer
来创建 store
对象。
这样 Redux 中就以对象的形式存储着每个组件的数据。
// redux/store.js
import { createStore, applyMiddleware, combineReducers } from 'redux'
import countReducer from './reducers/count'
import personReducer from './reducers/person'
import thunk from 'redux-thunk'
// 这样就可以同时管理多个组件的状态
const Reducers = combineReducers({
total: countReducer,
personList: personReducer,
})
export default createStore(Reducers, applyMiddleware(thunk))
Person
组件中获取 Redux 保存的状态,包括其他组件的数据。
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { createAddPersonAction } from '../../redux/actions/person'
import { nanoid } from 'nanoid'
class Person extends Component {
addPerson = () => {
const name = this.nameInput.value
const age = this.ageInput.value
const personObj = { id: nanoid(), name, age }
this.props.addPerson(personObj)
this.nameInput.value = ''
this.ageInput.value = ''
}
render() {
return (
<div>
<h2>在Person组件拿到Count组件的数据:{this.props.count}</h2>
<input type="text" ref={(c) => (this.nameInput = c)} placeholder="Please input name" />
<input type="text" ref={(c) => (this.ageInput = c)} placeholder="Please input age" />
<button onClick={this.addPerson}>添加</button>
<ul>
{this.props.personList.map((item) => {
return (
<li key={item.id}>
{item.name} -- {item.age}
</li>
)
})}
</ul>
</div>
)
}
}
export default connect(
// state 是 Redux 保存的状态对象
// 容器组件从 Redux 中取出需要的状态,并传递给 UI 组件
state => ({personList: state.personList, count: state.total}),
{
addPerson: createAddPersonAction
// 这一行凑数的,为了保持代码格式
addPerson2: createAddPersonAction
}
)(Person)
运行命令:npm run build
进行项目打包,生成 build
文件夹存放着打包完成的文件。
运行命令:npm i serve -g
全局安装 serve
,它能够以当前目录为根目录开启一台服务器,进入 build
文件夹所在目录,运行 serve
命令即可开启服务器查看项目效果。
或者 serve build 运行指定文件夹 + port 可以指定端口等
对象式:setState(stateChange, [callback])
stateChange
为状态改变对象(该对象可以体现出状态的更改)callback
是可选的回调函数, 它在状态更新完毕、界面也更新后才被调用函数式:setState(updater, [callback])
说明:
count
值是上一次的值,而非更新后的。可在第二个参数回调中获取更新后的状态。add = () => {
this.setState({ count: this.state.count + 1 })
console.log(this.state.count)
}
add = () => {
this.setState({ count: this.state.count + 1 }, () => {
console.log(this.state.count)
})
}
callback
回调在 componentDidMount
钩子之后执行add = () => {
this.setState((state, props) => {
return { count: state.count + props.step }
})
}
this.setState({ count: this.state.count + 1 })
import React, { Component, lazy, Suspense } from 'react'
import Loading from './Loading'
// 通过 lazy 函数配合 import() 函数动态加载路由组件
// 路由组件代码会被分开打包
const Home = lazy(() => import('./Home'))
const About = lazy(() => import('./About'))
export default Demo extends Component {
render() {
return (
<div>
<h1>Demo 组件</h1>
<Link to="/home">Home</Link>
<Link to="/about">About</Link>
// 通过 指定在加载得到路由打包文件前显示一个自定义 Loading 界面
<Suspense fallback={Loading}>
<Switch>
<Route path="/home" component={Home}>
<Route path="/about" component={About}>
</Switch>
</Suspense>
</div>
)
}
}
Hook 是 React 16.8.0 增加的新特性,让我们能在函数式组件中使用
state
和其他特性
State Hook
让函数式组件也可拥有 state
状态。const [Xxx, setXxx] = React.useState(initValue)
useState()
参数:状态初始化值;返回值:包含 2 个元素的数组,分别为状态值和状态更新函数setXxx(newValue)
setXxx(value => newValue)
React.useState
,不能使用对象!const [count, setCount] = React.useState(0)
const [name, setName] = React.useState('Tom')
function add() {
setCount(count + 1)
setCount((count) => count + 1)
}
Effect Hook
让我们能在函数式组件中执行副作用操作(就是模拟生命周期钩子)Effect Hook
可以模拟三个钩子:componentDidMount
、componentDidUpdate
、componentWillUnmount
React.useEffect
第一个参数 return
的函数相当于 componentWillUnmount
,若有多个会按顺序执行// 语法
React.useEffect(() => {
...
return () => {
// 组件卸载前执行,即 componentWillUnmount 钩子
...
}
}, [stateValue]) // [stateValue] 为监视state的某个属性
// 模拟 componentDidMount
// 第二个参数数组为空,表示不监听任何状态的更新
// 因此只有页面首次渲染会执行输出
React.useEffect(() => {
console.log('DidMount')
return () => {
console.log('WillUnmount 1')
}
}, [])
// 模拟全部状态 componentDidUpdate
// 若第二个参数不写,表示监听所有状态的更新
React.useEffect(() => {
console.log('All DidUpdate')
return () => {
console.log('WillUnmount 2')
}
})
// 模拟部分状态 componentDidUpdate
// 第二个参数数组写上状态,表示只监听这些状态的更新
React.useEffect(() => {
console.log('Part DidUpdate')
return () => {
console.log('WillUnmount 3')
}
}, [count, name])
// 若调用 ReactDOM.unmountComponentAtNode(document.getElementById('root'))
// 会输出 WillUnmount 1、2、3
Ref Hook
可以在函数式组件存储或查找组件内的标签或其他数据const refContainer = React.useRef()
React.createRef()
类似,也是专人专用function Demo() {
const myRef = React.useRef()
function show() {
console.log(myRef.current.value)
}
return (
<div>
<input type="text" ref={myRef} />
<button onClick={show}>展示数据</button>
</div>
)
}
Fragment
标签本身不会被渲染成一个真实 DOM 标签,有点像 Vue 的 template
。Fragment
标签可以传递 key
属性,遍历时候可用。import React, { Component, Fragment } from 'react'
export default class Demo extends Component {
render() {
return (
<Fragment key={1}>
<input type="text" />
<input type="text" />
</Fragment>
)
// 或
return (
<>
<input type="text" />
<input type="text" />
</>
)
}
}
Context 是一种组件间通信方式,常用于祖父组件与子孙组件。实际开发一般不用,一般用 React-Redux
用法说明:
1) 创建Context容器对象:
const XxxContext = React.createContext()
2) 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
<XxxContext.Provider value={数据}>
子组件
</XxxContext.Provider>
3) 后代组件读取数据:
// 第一种方式:仅适用于类组件
// 声明接收context
static contextType = xxxContext
// 读取context中的value数据
this.context
//第二种方式: 可用于函数组件与类组件
<XxxContext.Consumer>
{
// value就是context中的value数据
value => (
...
)
}
</XxxContext.Consumer>
举个栗子:
// context.js
import React from 'react'
export const MyContext = React.createContext()
export const { Provider, Consumer } = MyContext
// A.jsx
class A extends Component {
state = { username: 'tom', age: 18 }
render() {
const { username, age } = this.state
return (
<div>
<h3>A组件</h3>
<h4>用户名是:{username}</h4>
<Provider value={{ username, age }}>
<B />
</Provider>
</div>
)
class B extends Component {
render() {
return (
<div>
<h3>B组件</h3>
<C />
</div>
)
// 后代c 要使用祖组件传来的value
class C extends Component {
// 先声明接受
static contextType = MyContext
render() {
// 然后就可以从this。context上读取传来的值,不声明就没有值
const { username, age } = this.context
return (
<div>
<h3>C组件</h3>
<h4>
从A组件接收到的用户名:{username},年龄:{age}
</h4>
</div>
)
// 函数式组件使用
function C() {
return (
<div>
<h3>我是C组件</h3>
<h4>
从A组件接收到的用户名:
// 在要使用的地方 用Consumer 包裹住,里面传一个value value里既有传来的属性
<Consumer>
{(value) => `${value.username},年龄是${value.age}`}
</Consumer>
</h4>
</div>
)
问题:
setState()
,即使没有修改状态,组件也会重新 render()
render()
原因:
shouldComponentUpdate()
钩子默认总是返回 true
改进:
state
或 props
的数据发生改变时才重新渲染方式:
shouldComponentUpdate(nextProps, nextState)
的逻辑,只有数据发生改变才返回 true
PureComponent
,它重写了 shouldComponentUpdate()
, 只有 state
或 props
数据有变化才返回 true
TIP
- 它只是进行
state
和props
数据的浅比较, 如果只是数据对象内部数据变了, 返回false
。即对于引用数据类型,比较的是地址引用- 不要直接修改
state
数据, 而是要产生新数据
import React, { PureComponent } from 'react'
class Demo extends PureComponent {
...
addStu = () => {
// 不会渲染
const { stus } = this.state
stus.unshift('小刘')
this.setState({ stus })
// 重新渲染
const { stus } = this.state
this.setState({ stus: ['小刘', ...stus] })
}
...
}
类似于 Vue 中的插槽技术
如何向组件内部动态传入带内容的结构(即标签或组件)?
children props
:通过组件标签体传入结构render props
:通过组件标签属性传入结构,可携带数据children props
方式:
this.props.children
中使用 children props
:
import React, { Component } from 'react'
export default class Parent extends Component {
render() {
return (
<div>
<h3>Parent组件</h3>
<A>
<B />
</A>
</div>
)
}
}
class A extends Component {
state = { name: 'tom' }
render() {
return (
<div>
<h3>A组件</h3>
{this.props.children}
</div>
)
}
}
class B extends Component {
render() {
return (
<div>
<h3>B组件</h3>
</div>
)
}
}
render props
方式:
} />
{this.props.render(name)}
import React, { Component } from 'react'
export default class Parent extends Component {
render() {
return (
<div>
<h3>Parent组件</h3>
// 调用一个render 内联一个函数携带数据参数,并返回一个组件标签 传递参数
// 可以将B换成任意想展示的组件或内容
<A render={(name) => <B name={name} />} />
</div>
)
}
}
class A extends Component {
state = { name: 'tom' }
render() {
const { name } = this.state
return (
<div>
<h3>A组件</h3>
// 在指定位置用了 this。props。render(xxx)既可以指定传来的组件展示的地方
// 并将属性传递给B组件
{this.props.render(name)}
</div>
)
}
}
class B extends Component {
render() {
return (
<div>
<h3>B组件,{this.props.name}</h3>
</div>
)
}
}
TIP
错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面。
注意:只在生产环境(项目上线)起效
特点:
import React, { Component } from 'react'
import Child from './Child'
export default class Parent extends Component {
state = {
//用于标识子组件是否产生错误
hasError: '',
}
// 当子组件出现错误,会触发调用,并携带错误信息
static getDerivedStateFromError(error) {
// render 之前触发
// 返回新的 state
return { hasError: error }
}
componentDidCatch(error, info) {
console.log(error, info)
console.log('此处统计错误,反馈给服务器')
}
render() {
return (
<div>
<h2>Parent组件</h2>
{this.state.hasError ? <h2>网络不稳定,稍后再试</h2> : <Child />}
</div>
)
}
}
props
pubs-sub
推荐搭配:
props
conText
(开发用的少,封装插件用的多即 React-Redux)你想在项目中使用基于类的 Hook 逻辑,并且目前无法将这些类组件重写为 Hooks。类可能太复杂了,或者如果你更改它,可能会破坏项目中的许多其他内容。这种方法的商业价值也值得怀疑。如果你转至 React 文档,会看到一个有趣的声明:
HOC 是重用组件逻辑的高级 React 技术,其使我们能够在现有类组件中使用 Hook 逻辑。因为 HOC 是使一个组件作为输入,并通过一些额外的 props 返回相同的组件。在我们的情况下,我们将传递 Hook 函数作为 props。
import React from 'react';
import { useScreenWidth } from '../hooks/useScreenWidth';
export const withHooksHOC = (Component: any) => {
return (props: any) => {
const screenWidth = useScreenWidth();
return <Component width={screenWidth} {...props} />;
};
};
复制代码
最后一步是用该HOC简单包装我们现有的类组件。然后,我们仅使用width属性作为传递给组件的其他属性。
import React from 'react';
import { withHooksHOC } from './withHooksHOC';
interface IHooksHOCProps {
width: number;
}
class HooksHOC extends React.Component<IHooksHOCProps> {
render() {
return <p style={{ fontSize: '48px' }}>width: {this.props.width}</p>;
}
}
export default withHooksHOC(HooksHOC);
import React,{ useState , useEffect , useCallback ,Component} from 'react'
function useWinsize(){
//返回值:包含 2 个元素的数组,分别为状态值和状态更新函数
const [size,SetSize] = useState({
// 参数为初始值
width:document.documentElement.clientWidth,
height:document.documentElement.clientHeight,
})
// useCallback 用于缓存方法 useMemo 用于缓存属性 状态
const onResize = useCallback(()=>{
// 在此调用方法可以将方法缓存起来提高效率
SetSize({
// 更新数据,直接传newValue 值第一种更新用法
width:document.documentElement.clientWidth,
height:document.documentElement.clientHeight
})
},[]) // 第二个参数传空数组,表示didMount 只执行一次
useEffect(()=>{
// 当didmount 时执行一遍添加一个窗口监听事件,当 触发resize 事件时,执行 onResize 的回调函数更新窗口
window.addEventListener('resize',onResize)
// 调用unMount 组件卸载时移除组件监听事件,防止在其他组件继续监听
return ()=>{
window.removeEventListener('resize',onResize)
}
},[])
// 调用该函数返回size 给使用者
return size
}
//组件中使用
export default function MyHooks (){
const size = useWinsize()
console.log(size);
return <div>size:{size.width}x{size.height}</div>
}
// App.jsx ----------------------------------------
import React, { Component } from 'react'
// 使用Hook
import Win from './react_hook/UseClientHeigth'
export default class App extends Component {
render() {
return (
<div>
<Win/>
</div>
)
}
}
import React, { useState, useEffect } from 'react'
const usePerson = (name) => {
const [loading, setLoading] = useState(true)
const [person, setPerson] = useState({})
useEffect(() => {
setLoading(true)
setTimeout(()=> {
setLoading(false)
setPerson({name})
},2000)
},[name])
return [loading,person]
}
const AsyncPage = ({name}) => {
const [loading, person] = usePerson(name)
return (
<>
{loading?<p>Loading...</p>:<p>{person.name}</p>}
</>
)
}
const PersonPage = () =>{
const [state, setState]=useState('')
const changeName = (name) => {
setState(name)
}
return (
<>
<AsyncPage name={state}/>
<button onClick={() => {changeName('名字1')}}>名字1</button>
<button onClick={() => {changeName('名字2')}}>名字2</button>
</>
)
}
export default PersonPage
import React, { useState, useEffect } from 'react'
const useMousePosition = () => {
const [position, setPosition] = useState({x: 0, y: 0 })
useEffect(() => {
const updateMouse = (e) => {
setPosition({ x: e.clientX, y: e.clientY })
}
document.addEventListener('mousemove', updateMouse)
return () => {
document.removeEventListener('mousemove', updateMouse)
}
})
return position
}
export default useMousePosition
// 需要引入时
import React, { Component } from 'react'
// 使用Hook
import useMousePosition from './react_hook/useMouse'
// 注意只能在函数里使用
export default function App(){
const position = useMousePosition()
return (
<div>
{position.x},{position.y}
</div>
)
}