React hooks详解
此篇文章仅是对hooks入门的总结,老鸟略过吧~
React从16.8.X以后增加了一个新特性,react hooks 让我们看看这个新特性又带来了哪些惊喜呢~以下内容我们采取不同方式创建组件来进行对比总结
组件的创建方式:
用过react的都了解,传统react创建组件提供了两种方式,函数式与类(class)
class创建无状态组件
class App extends React.Component {
constructor(props) {
super(props);
} render() {
return <div>
<p>{this.props.name}</p>
</div>
}
} function renderApp() {
let appProps = {
name: 'dqhan'
}
ReactDOM.render(
<App {...appProps} />,
document.getElementById('app')
)
} renderApp();
添加状态管理
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
name: props.name
};
this.handleChangeName = this.handleChangeName.bind(this);
} handleChangeName() {
this.setState({
name: '我变了'
})
} render() {
return (
<React.Fragment>
<p> {`hello~${this.state.name}`} </p>
<button onClick={this.handleChangeName}></button>
</React.Fragment>
);
}
}
我们通过class可以实现无状态组件以及常规组件通过setState的状态管理。
函数式创建组件
function App(props) {
return <p>{`hello! ${props.name}`}</p>
} function renderApp() {
let appProps = { name: "dqhan" };
ReactDOM.render(<App {...appProps} />, document.getElementById("app"));
}
函数式创建组件通常是无状态组件,这种方式没有办法在内部对状态统一管理,如果我们非要添加状态管理呢,那就只能借助redux啦~或者我们自己利用观察者模式实现一个发布订阅(emmmm比较勉强吧,毕竟实际开发中我们不可能这么做)
那么如果我们非要这么做呢,正题来了,React版本在16.8.X以后增添了一个新特性就是hooks。
hooks涉及API有useState、 useEffect、 useCallback、 useRef、 useMemo、 React.memo、 useReducer等,具体可以参考官方文档,我们来看一下hooks怎么用
React Hooks创建组件
无状态组件
function App(props) {
return <div>
<p>{`hello~${props.name}`}</p>
</div>
}
哈哈,跟函数式一样,毕竟我们要在函数式组件里添加状态管理嘛
1.useState
作用:添加状态管理
function App() {
let [name, setName] = useState('dqhan');
return <div>
<p>{`hello~${name}`}</p>
<button onClick={() => setName('我变了')}>Click</button>
</div>;
}
react hook可以管理自己的状态,有自己的函数钩子,这点相比要函数式显然效果更好,不需要借助redux,这就是我们为啥要在前面提到函数式编程涉及状态管理问题,就是要在这里跟react hook做个比较。
到这里我们知道了这个useState,多了一个useState让函数式创建类有了自己的持久状态。那么在函数式里面我们如何做到class组件中的setState呢?
function App(props) {
let [name, setName] = useState('dqhan');
let handleChangeName = useCallback(() => {
setName(preState => {
let updatedValues = {
newValue: '我变了'
}
return { ...{ preState }, ...updatedValues }
})
})
function click1(params) {
setName('我变了1')
}
return <div>
<p>{`hello~${name}`}</p>
<button onClick={handleChangeName}>Click1</button>
<button onClick={click1}>Click2</button>
</div>;
}
这中方式已经实现了状态整合,但是我们如果模拟一个state呢,来统一管理state呢,我们可以这么实现
function App(props) {
let [state, setState] = useState({
name: 'dqhan'
});
let handleChangeName = useCallback(() => {
setState(preState => {
let updatedValues = {
name: '我变了'
}
return { ...preState, ...updatedValues }
})
})
return <div>
<p>{`hello~${state.name}`}</p>
<button onClick={handleChangeName}>Click</button>
</div>;
}
到目前为止,已经知道了react hook中如何使用state,那么周期函数呢,那么就涉及另一个钩子useEffect
2.useEffect
作用:周期函数
useEffect涉及三个周期函数 componentDidMount 、componentDidUpdate、 compinentWillUmount 我们看一个简单的实例
function App() {
var [count, setCount] = useState(0);
useEffect(() => {
console.log(`update--${count}`);
}, [count]);
return (
<div>
<button onClick={() => setCount(count + 1)}>Click</button>
</div>
);
}
function App() {
let [count, setCount] = useState(0);
useEffect(
() => {
//默认每一次渲染都需要执行的方法
console.log('didmount')
//如果需要实现componentWillComponent,则return 一个函数即可
return function unmount() {
console.log('unmount')
}
}
) let handleSetCount = useCallback(() => {
setCount((preCount) => {
let updatedCount = preCount + 1;
return updatedCount;
});
}) return <React.Fragment>
{console.log('render')}
<p>{`${count}`}</p>
<button onClick={handleSetCount}>Click</button>
</React.Fragment>
}
我们可以看一下执行周期
第一次渲染时候执行 render didmount
点击事件执行顺序 render unmount didmount
不难看出,每一次渲染我们都会执行render进行渲染,然后清除掉上一次的useEffect,然后渲染完成之后重新执行useEffect
这样通过一个useEffec可以默认执行两个周期函数,也就是当我们需要对组件添加一些需要当组件卸载时候清除掉的功能时候,这个是很方便的,常见的就是setTimeout setIntrval等定时器
但是比如一个component渲染之后我们通常会发送一个请求来请求数据,然后重写渲染这个组件,这样会造成死循环怎么办,我们可以在useEffect后添加第二个参数
阻止useEffect每一次都要执行
function App() {
let [count, setCount] = useState(0);
useEffect(
() => {
//默认每一次渲染都需要执行的方法
console.log('didmount')
//如果需要实现componentWillComponent,则return 一个函数即可
return function unmount() {
console.log('unmount')
}
},
[setCount]
) let handleSetCount = useCallback(() => {
setCount((preCount) => {
let updatedCount = preCount + 1;
return updatedCount;
});
}) return <React.Fragment>
{console.log('render')}
<p>{`${count}`}</p>
<button onClick={handleSetCount}>Click</button>
</React.Fragment>
}
当传入第二个参数得值不变得时候就会跳过useEffect函数执行
如何模拟componentDidMount与componentWillUmount,第二个参数我们传一个空数组,这样就可以实现仅当组件渲染跟组件卸载得时候执行
function App() {
let [count, setCount] = useState(0);
useEffect(
() => {
//默认每一次渲染都需要执行的方法
console.log('didmount')
//如果需要实现componentWillComponent,则return 一个函数即可
return function unmount() {
console.log('unmount')
}
},
[]
) let handleSetCount = useCallback(() => {
setCount((preCount) => {
let updatedCount = preCount + 1;
return updatedCount;
});
}) return <React.Fragment>
{console.log('render')}
<p>{`${count}`}</p>
<button onClick={handleSetCount}>Click</button>
</React.Fragment>
}
不过,这隐藏了一个问题:传递空数组容易出现问题。如果咱们添加了依赖项,那么很容易忘记向其中添加项,如果错过了一个依赖项,那么该值将在下一次运行useEffect时失效,并且可能会导致一些奇怪的问题。
常见得就是当我们想用父组件调用子组件时候使用得ref,或者我们要获取dom焦点时
function App() {
let [count, setCount] = useState(0);
let [value, setValue] = useState('')
const inputRef = useRef(); useEffect(
() => {
//默认每一次渲染都需要执行的方法
console.log('didmount') //如果需要实现componentWillComponent,则return 一个函数即可
return function unmount() {
console.log('unmount')
}
},
[inputRef]
) let handleSetCount = useCallback(() => {
setCount((preCount) => {
let updatedCount = preCount + 1;
return updatedCount;
});
}) let handleSetValue = function (e) {
setValue(e.target.value);
} return <React.Fragment>
{console.log('render')}
<p>{`${count}`}</p>
<input
ref={inputRef}
value={value}
onChange={handleSetValue}
></input>
<button
ref={inputRef}
onClick={handleSetCount}
>Click</button> </React.Fragment>
}
下面我们实现一个请求实例
请求实例
function App() {
let [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
const result = await axios(config);
setData(result);
}; fetchData();
}, []); return <div></div>;
}
利用hook可以做到分离接口
function useFetchHook(config, watch) {
let [data, setData] = useState(null);
let [status, setStatus] = useState(0);
useEffect(
() => {
const fetchData = async () => {
try {
const result = await axios(config);
setData(result);
setStatus(0);
} catch (e) {
setStatus(1);
}
fetchData();
};
},
watch ? [...watch] : []
); return { data, status };
}
现在我们整体上知道了useState useEffect怎么使用了,我们来自己实现一个简易的
实现useState
var val;
function useState(initVal) {
let resultVal = val || initVal;
function setVal(newVal) {
resultVal = newVal;
render();
}
return [resultVal, setVal]
}
实现useEffect
var watchArr = [];
function useEffect(fn, watch) {
var hasWatchChange = true;
hasWatchChange = watchArr && watch.every((val, i) => val === watchArr[i])
if (hasWatchChange) {
fn();
watchArr = watch;
}
}
hooks里面最常用的两个API就是useState与useEffect,现在是不是已经了解了呢,下面我们介绍一些其他API
3.useContext
作用:越级别获取组件内容
类组件中我们也常用context,类组件实现方式
class App extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<AppContext.Provider value="dark">
<Target />
</AppContext.Provider>
);
}
} class Target extends React.Component {
//通过定义静态属性 contextType 来订阅
//没有定义是获取不到的
static contextType = AppContext;
render() {
console.log(this.context);
return <div></div>;
}
}
Hooks实现方式
const AppContext = React.createContext('target'); function App() {
useEffect(
() => { },
[]
);
return <AppContext.Provider value="dark">
<Target />
</AppContext.Provider>;
} function Target() {
const value = useContext(AppContext);
console.log(value);
return <div></div>;
}
在需要订阅多个 context 的时候,就更能体现出useContext的优势。
传统的实现方式
function App() {
return <CurrentUser.Consumer>
{
user => <Notifications.Consumer>
{notifications =>
<header>
Welcome back, {user.name}!
You have {notifications.length} notifications.
</header>
}
</Notifications.Consumer>
}
</CurrentUser.Consumer>
}
hooks实现
function App() {
const user = useContext(CurrentUser);
const notifications = useContext(Notifications); return (
<header>
Welcome back, {user.name}!
You have {notifications.length} notifications.
</header>
);
}
是不是比传统的要简单的多
4.useReducer
作用:复杂状态管理,跟redux本质上是一样的
函数式组件如果涉及到状态管理,我们需要借助redux,那么hooks需要吗,答案也是一样的,简单的状态管理我们可以通过useState来进行管理,如果比较复杂的状态管理呢,react hook给我们提供了方法 useReducer
function init(initialCount) {
return { count: initialCount };
} function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return init(action.payload);
default:
throw new Error();
}
} function Counter({ initialCount }) {
const [state, dispatch] = useReducer(reducer, initialCount, init);
return (
<>
Count: {state.count}
<button
onClick={() => dispatch({ type: 'reset', payload: initialCount })}>
Reset
</button>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</>
);
}
5.useCallback
作用:提升性能,缓存事件,减少没必要的渲染
当我们使用类组件创建时,我们会怎么绑定事件呢
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div>
<p>{`hello~${name}`}</p>
<button onClick={() => { console.log('click') }}>Click</button>
</div>
}
}
这样写会导致什么结果呢,就是当渲染的时候react会认为每一次绑定的事件都是新的,从而从新进行计算
改进如下
class App extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log('click')
}
render() {
return <div>
<p>{`hello~${name}`}</p>
<button onClick={this.handleClick}>Click</button>
</div>
}
}
我们讲触发函数绑定在this上,来缓存这个方法
hooks
function App() {
let [count, setCount] = useState(0);
return <div>
<button onClick={() => setCount(1)} ></button>
</div>
}
同样的问题这么写也是存在的,改进如下
function App() {
let [count, setCount] = useState(0);
let handleSetCount = useCallback(() => {
setCount(1);
})
return <div>
<button onClick={handleSetCount} ></button>
</div>
}
我们通过useCallback来缓存这个事件达到优化效果
6.useMemo
作用:提升性能,选择性的渲染变化组件
function App(target, target2) {
const target = useMemo(() => {
return <Target />
}, [target])
const target2 = useMemo(() => {
return <Target2 />
}, [target2])
return <div>
{target}
{target2}
</div>
}
当target变化仅渲染Target组件,同理也作用与Target2组件
React.memo
作用:提升性能
如果想实现class中的shouldComponentUpdate方法呢 ,区别是它只能比较 props,不会比较 state:
const App = React.mome((target, target2) => {
const target = useMemo(() => {
return <Target />
}, [target])
const target2 = useMemo(() => {
return <Target2 />
}, [target2])
return <div>
{target}
{target2}
</div>
})
7.useRef
作用:获取dom依赖关系
类组件实现方式
class App extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
} componentDidMount() {
this.myRef.current.focus();
} render() {
return <input ref={this.myRef} type="text" />;
}
}
hooks
function App() {
let [value, setValue] = useState('')
const inputRef = useRef(); useEffect(
() => {
},
[inputRef]
) let handleSetValue = function (e) {
setValue(e.target.value);
} return <React.Fragment>
<input
ref={inputRef}
value={value}
onChange={handleSetValue}
></input> </React.Fragment>
}
好了hooks的基本使用方式就介绍完了,现在你对hook多少能了解了一些吧~
React hooks详解的更多相关文章
- React.Children详解
React.Children提供了处理this.props.children的工具,this.props.children可以任何数据(组件.字符串.函数等等).React.children有5个方法 ...
- vue和react全面对比(详解)
vue和react对比(详解) 放两张图镇压小妖怪 本文先讲共同之处, 再分析区别 大纲在此: 共同点: a.都使用虚拟dom b.提供了响应式和组件化的视图组件 c.注意力集中保持在核心库,而将其他 ...
- React Native组件、生命周期及属性传值props详解
创建组件的三种方式 第一种:通过ES6的方式创建 /** * 方式一 :ES6 */ export default class HelloComponent extends Component { r ...
- 4-13 Webpacker-React.js; 用React做一个下拉表格的功能: <详解>
Rails5.1增加了Webpacker: Webpacker essentially is the decisions made by the Rails team and bundled up i ...
- react基本demo详解
一.react的优势 1.React速度很快:它并不直接对DOM进行操作,引入了一个叫做虚拟DOM的概念,安插在javascript逻辑和实际的DOM之间,性能好. 2.跨浏览器兼容:虚拟DOM帮助我 ...
- React 实践心得:react-redux 之 connect 方法详解
Redux 是「React 全家桶」中极为重要的一员,它试图为 React 应用提供「可预测化的状态管理」机制. Redux 本身足够简单,除了 React,它还能够支持其他界面框架.所以如果要将 R ...
- React—组件生命周期详解
React—组件生命周期详解 转自 明明的博客 http://blog.csdn.net/slandove/article/details/50748473 (非原创) 版权声明:转载请注明出处,欢 ...
- react目录结构、demo实例详解、属性数据绑定方式
1.目录结构 2.demo实例详解 a)创建Home.js import React, { Component } from 'react'; //创建一个组件必须要集成Component组件,且组件 ...
- 8分钟为你详解React、Angular、Vue三大前端技术
[引言] 当前世界中,技术发展非常迅速并且变化迅速,开发者需要更多的开发工具来解决不同的问题.本文就对于当下主流的前端开发技术React.Vue.Angular这三个框架做个相对详尽的探究,目的是为了 ...
随机推荐
- android支付宝首页、蚂蚁森林效果、视频背景、校园电台、载入收缩动画等源码
Android精选源码 android实现蚂蚁森林效果源码 android仿支付宝首页应用管理(拖拽排序,添加删除) android校园网络电台客户端源码 android实现按钮伸缩效果源码 一款仿i ...
- Java IO: Buffered和Data
作者:Jakob Jenkov 译者: 李璟(jlee381344197@gmail.com) 本小节会简要概括Java IO中Buffered和data的输入输出流,主要涉及以下4个类型的流:Bu ...
- mac android sdk manager 无法更新(被墙)
http://www.androiddevtools.cn/ 一句话,相见恨晚!! 想把以前的旧安卓项目拿到MAC上 环境就卡住了,以前的包是4.4的,想试试5.0的,更新不动 Android Too ...
- hadoop报错:could only be replicated to 0 nodes, instead of 1
错误 [root@hadoop test]# hadoop jar hadoop.jarcom.hadoop.hdfs.CopyToHDFS 14/01/26 10:20:00 WARN hdfs.D ...
- sm4算法(附源码、测试代码)
from:http://blog.csdn.net/mao0514/article/details/52930944 SM4是我们自己国家的一个分组密码算法,是国家密码管理局于2012年发布的.网址戳 ...
- 吴裕雄--天生自然 R语言开发学习:功效分析(续一)
#----------------------------------------# # R in Action (2nd ed): Chapter 10 # # Power analysis # # ...
- JAVAscript的DOM操作及实例
一.Windows对象操作 (1)用代码打开窗口:window.open("第一部分","第二部分","第三部分","第四部分&q ...
- YOLO 论文阅读
YOLO(You Only Look Once)是一个流行的目标检测方法,和Faster RCNN等state of the art方法比起来,主打检测速度快.截止到目前为止(2017年2月初),YO ...
- C++走向远洋——35(友元,时间)
*/ * Copyright (c) 2016,烟台大学计算机与控制工程学院 * All rights reserved. * 文件名:time.cpp * 作者:常轩 * 微信公众号:Worldhe ...
- linux lsof常用方法
lsof简介 lsof(list open files)是一个列出当前系统打开文件的工具,在linux环境下,任何事物都是以文件形式存在,通过文件不仅仅可以访问常规数据,还可以访问网络连接和硬件.系统 ...