组件类的缺点
React 的核心是组件。v16.8 版本之前,组件的标准写法是类(class)。
下面是一个简单的组件类。
import React, { Component } from "react";
export default class Button extends Component {
constructor() {
super();
this.state = { buttonText: "Click me, please" };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(() => {
return { buttonText: "Thanks, been clicked!" };
});
}
render() {
const { buttonText } = this.state;
return <button onClick={this.handleClick}>{buttonText}</button>;
}
}
这个组件类仅仅是一个按钮,但可以看到,它的代码已经很"重"了。真实的 React App 由多个类按照层级,一层层构成,复杂度成倍增长。再加入 Redux,就变得更复杂。
Redux 的作者 总结了组件类的几个缺点。
- 大型组件很难拆分和重构,也很难测试。
- 业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。
- 组件类引入了复杂的编程模式
函数组件
React 团队希望,组件不要变成复杂的容器,最好只是数据流的管道。开发者根据需要,组合管道即可。 组件的最佳写法应该是函数,而不是类。
React 早就支持函数组件,下面就是一个例子。
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
但是,这种写法有重大限制,必须是纯函数,不能包含状态,也不支持生命周期方法,因此无法取代类。
React Hooks
React Hooks 的设计目的,就是加强版函数组件,完全不使用"类",就能写出一个全功能的组件。
Hook 这个单词的意思是"钩子"。
React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。 React Hooks 就是那些钩子。
React 常用Hook
- useState
- useEffect
- useRef
- useContext
- useReducer
- useCallback
- useMemo
useState
- useState()用于为函数组件引入状态(state)。纯函数不能有状态,所以把状态放在钩子里面。
//引入钩子函数
import {useState} from 'react';
const Test = () => {
// useState的参数为状态初始值
// useSibtate的返回值是一个数组
// 数组第一个成员是变量,第二个成员是函数,用来更新状态,约定是set前缀加上状态的变量名
const [num,setNum] = useState(1)
return (
<div>
<div>访问num: {num}</div>
<button onClick={()=>setNum(num+1)}>修改state</button>
</div>
);
}
export default Test
useEffect副作用钩子
useEffect()用来引入具有副作用的操作,最常见的就是向服务器请求数据。
useEffect() 可以实现生命周期函数的效果
import { useState, useEffect } from 'react';
const Test = () => {
const [num, setNum] = useState(1)
const [msg, setMsg] = useState('a')
const [list, setList] = useState([])
// 组件初始化自动执行一次,之后每更新一次执行一次
// 类似 componentDidUpdate
useEffect(()=>{
console.log('没有第二个参数,多次执行');
})
//等价于 componentDidMount , 只在初始化时执行一次
useEffect(()=>{
console.log('第二个参数为空数组,只执行一次');
},[])
// 第二个参数为state, 初次执行后,只有该state改变时执行
useEffect(()=>{
console.log('第二个参数为state数据,数据改变一次执行一次');
},[num])
useEffect(()=>{
//return的回调 等价于 componentWillUnmount() 组件卸载时
return ()=>{
console.log('组件卸载时');
}
},[])
return (
<div>
<div>访问num: {num}</div>
<button onClick={() => setNum(num + 1)}>修改num</button>
<hr/>
<div>访问msg: {msg}</div>
<button onClick={() => setMsg(msg + 'm')}>修改msg</button>
</div>
);
}
export default Test
在父组件中实现组件卸载
import Test from './Test'
import { useState } from 'react';
const App = () => {
const [flag,setFlag] = useState(true)
return (
<div>
{/* 单击按钮,卸载组件 */}
<button onClick={()=>{setFlag(false)}}>删除Test</button>
{ flag ? <Test></Test> : null}
</div>
);
}
useRef
useRef用来获取DOM元素
import { useRef , useEffect} from "react";
const Test = () => {
//1. 创建引用实例
const btnRef = useRef()
useEffect(()=>{
//3. 通过实例的current属性即可访问DOM对象
btnRef.current.focus()
},[])
return (
<div>
{/* 2. 给DOM元素添加ref属性,值为useRef创建的实例 */}
<input type="text" ref={btnRef}/>
</div>
);
}
export default Test
useContext 共享状态钩子
如果需要在组件之间共享状态,可以使用useContext()
- 使用 React Context API,在组件外部建立一个 Context
Context因为需要其它组件共享,所以要单独导出
export const AppContext = createContext()
- 利用父组件状态钩子,创建共享状态
const [city,setCity] = useState('郑州')
- 提供了一个 Context 对象,这个对象可以被子组件共享
value值为共享状态,值为一个对象
<AppContext.Provider value={{city,setCity}}>
Test---{city}
<hr/>
<Child></Child>
</AppContext.Provider>
4、在后代组件中引入Context,
import {AppContext} from './Test'
- useContext()钩子函数用来引入 Context 对象,从中获取所需要的状态
import React,{useContext} from 'react';
......
const {city,setCity} = useContext(AppContext)
完整案例
Parent.js
import { createContext, useState } from 'react'
import Child from './Child'
export const AppContext = createContext()
const Parent = () => {
const [city, setCity] = useState('郑州')
return (
<AppContext.Provider value={{ city, setCity }}>
Parent---{city}
<hr />
<Child></Child>
</AppContext.Provider>
);
}
export default Parent;
Child.js
import GrandSon from './GrandSon'
const Child = () => {
return (
<div>
Child
<hr/>
<GrandSon></GrandSon>
</div>
);
}
export default Child;
GrandSon.js
import React,{useContext} from 'react';
import {AppContext} from './Test'
const GrandSon = () => {
const {city,setCity} = useContext(AppContext)
return (
<div>
GrandSon-----{city}
<div><button onClick={()=>{setCity('广州')}}>改变城市</button></div>
<hr/>
</div>
);
}
export default GrandSon;
useReducer
- useReducer 是用于提高应用性能的,当更新逻辑比较复杂时,我们应该考虑使用useReducer
- useReducer 是 useState的代替方案,用于 state 复杂变化
- useReducer 是单个组件状态管理,组件通讯还需要 props
- redux 是全局状态管理,多组件共享数据
useReducer 接受一个 reducer 函数作为参数,
reducer 接受两个参数一个是 state 另一个是 action 。然后返回一个状态 count 和 dispath,count 是返回状态中的值,
而 dispatch 可以发布事件来更新 state 。
import { useReducer } from 'react'
// 定义reducter,不像redux传递type
const reducer = (state,action)=> {
switch(action){
case 'add':
return state + 1;
case 'sub':
return state - 1;
default:
return state;
}
}
const Parent = () => {
// count为变量即状态 , dispatch用来发布事件改变状态
// useReducer第二个参数为count的初始值
const [count, dispatch] = useReducer(reducer,3)
return (
<div>
<div>count------{count}</div>
{/* 用dispatch发布事件 */}
<div><button onClick={()=>{dispatch('add')}}>add</button></div>
<div><button onClick={()=>{dispatch('sub')}}>sub</button></div>
</div>
);
}
export default Parent;
useMemo
函数组件的每一次调用都会执行其内部的所有逻辑,那么会带来较大的性能损耗。因此useMemo 和useCallback就是解决性能问题的杀手锏。
useCallback和useMemo的参数跟useEffect一致,他们之间最大的区别有是useEffect会用于处理副作用,而前两个hooks不能。
useMemo和useCallback都会在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行;并且这两个hooks都返回缓存的值,useMemo返回缓存的变量,useCallback返回缓存的函数。
反例: 不相关的val改变,expensive也会执行
import {useState} from 'react';
export default function WithoutMemo() {
const [count, setCount] = useState(1);
const [val, setValue] = useState('');
function expensive() {
console.log('compute');
let sum = 0;
for (let i = 0; i < count * 100; i++) {
sum += i;
}
return sum;
}
return <div>
<h4>{count}-{val}-{expensive()}</h4>
<div>
<button onClick={() => setCount(count + 1)}>+c1</button>
<input value={val} onChange={event => setValue(event.target.value)}/>
</div>
</div>;
}
用useMemo解决
import {useState,useMemo} from 'react';
export default function WithMemo() {
const [count, setCount] = useState(1);
const [val, setValue] = useState('');
// useMemo第一个参数是回调, 第二个是依赖的变量
const expensive = useMemo(() => {
console.log('compute');
let sum = 0;
for (let i = 0; i < count * 100; i++) {
sum += i;
}
return sum;
}, [count]);
return <div>
<h4>{count}-{expensive}</h4>
{val}
<div>
<button onClick={() => setCount(count + 1)}>+c1</button>
<input value={val} onChange={event => setValue(event.target.value)}/>
</div>
</div>;
}
useCallback
useCallback跟useMemo比较类似,但它返回的是缓存的函数。
分析下面的案例:
每次修改count,set.size就会+1,这说明useCallback依赖变量count,count变更时会返回新的函数;而val变更时,set.size不会变,说明返回的是缓存的旧版本函数。
import React, { useState, useCallback } from 'react';
const set = new Set();
export default function Callback() {
const [count, setCount] = useState(1);
const [val, setVal] = useState('');
const callback = useCallback(() => {
console.log(count);
}, [count]);
set.add(callback);
return <div>
<h4>{count}</h4>
<h4>{set.size}</h4>
<div>
<button onClick={() => setCount(count + 1)}>+</button>
<input value={val} onChange={event => setVal(event.target.value)}/>
</div>
</div>;
}
使用场景是:有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。
import React, { useState, useCallback, useEffect } from 'react';
export default function Parent() {
const [count, setCount] = useState(1);
const [val, setVal] = useState('');
//观察子组件userEffect在val值改变时不触发
// const callback = useCallback(() => {
// console.log('cb');
// return count;
// }, [count]);
//观察子组件userEffect在val值改变时触发
const callback = () => {
console.log('cb');
return count;
};
return <div>
<h4>{count}</h4>
<Child callback={callback}/>
<div>
<button onClick={() => setCount(count + 1)}>+</button>
<input value={val} onChange={event => setVal(event.target.value)}/>
</div>
</div>;
}
function Child({ callback }) {
console.log('child');
const [count, setCount] = useState(() => callback());
useEffect(() => {
console.log('useeffect');
setCount(callback());
}, [callback]);
return <div>
子------{count}
</div>
}
React-router 常用Hook
- useParams
- useHistory
- useLocation
import { useEffect } from 'react'
import { useParams,useLocation,useHistory } from 'react-router-dom'
const Star = () => {
//获取路由信息中location数据
console.log('useLocation',useLocation())
//获取路由信息中history,调用history中的方法,可实现编程式导航
console.log('useHistory',useHistory())
//获取动态路由的参数
let { id } = useParams()
//组件初始化时,获取id,请求数据
useEffect(() => {
console.log('获取' + id + '对应的数据')
}, [])
return (
<div>Star</div>
);
}
export default Star;
WithRouter的用法
默认只有路由组件的props才能获取路由信息,普通组件如何要获取路由信息,实现编程式导航,可以用withRouter来实现路由的注入
import { withRouter } from 'react-router-dom'
const CartChild = (props) => {
console.log('CartChild', props)
return (
<div>
CartChild
<hr />
<button onClick={() => { }}>回到首页</button>
</div>
);
}
export default withRouter(CartChild);
React.createElement的用法
调用 React.createElement 返回的是js对象
import React, { Component } from 'react';
import ReactDOM from 'react-dom'
let obj = React.createElement(
"div",
{
className: "page",
},
React.createElement('header', null, " Hello, This is React "),
React.createElement("h1", null, "Start to learn right now!"),
"Right Reserve."
);
ReactDOM.render(obj, document.getElementById("root"));
props.children的用法
类似vue中的插槽
父组件
import CartChild from './CartChild'
const Cart = () => {
return (
<div>
Cart
<hr />
<CartChild>
<div>
<h1>标签中间的内容</h1>
<div>第二块内容</div>
</div>
{/* <h1>标签中间的内容</h1> */}
</CartChild>
</div>
);
}
export default Cart;
子组件
import { withRouter } from 'react-router-dom'
const CartChild = (props) => {
console.log('CartChild', props)
return (
<div>
CartChild
<hr/>
{props.children}
</div>
);
}
export default withRouter(CartChild);