跳到主要内容

7 篇博文 含有标签「Hooks」

查看所有标签

使用useRef获取DOM元素

使用useRef获取DOM元素是目前useRef最常用的用法。

基本用法

function App() {
const box = useRef();

return (
<div ref={box}>
<button onClick={() => console.log(box)}>点击获取div</button>
</div>
)
}

image.png

使用useRef跨组件周期保存数据

即使组件重新渲染,保存的数据仍然还在,保存的数据被更改不会触发组件重新渲染。

基本用法

/**
* @description: 组件挂载完成之后开启一个循环定时器,使用useRef的保存数据功能
* 不会随着组件的更新而丢失数据
* @param {*}
* @return {*}
*/
function App() {
const [count,setCount] = useState(0)
const timeId = useRef;
useEffect(() => {
timeId.current = setInterval(() => {
setCount(count => count + 1);
},1000)
}, []);
const handleSetInterval = () => {
clearInterval(timeId.current)
}
return (
<div>
<h1>当前求和为{count}</h1>
<button onClick={handleSetInterval}>点我清除定时器</button>
</div>
)
}

这里我们需要注意的是数据并不是直接保存在useRef返回的这个对象中的,而是保存在这个对象的current属性上的。


JustinHooks阅读需 1 分钟

原生useState具有的几个特点

  1. 返回一个数组,数组的第一个参数值是状态值,第二个参数值是设置状态的方法。
  2. useState会判断传入的state是初始值还是已存在的值。
  3. useState可能被多次调用
  4. 调用设置状态的方法后需要重新渲染组件。

手写useState

第一步:用不同的数组来分别存放状态值和设置状态的方法

let states = [];
let setters = [];
let stateId = 0;

第二步:判断useState传入的初始状态值是否已经存在

存在用以前的,不存在则使用初始状态值

states[stateId] = states[stateId] ? states[stateId] : initialState;

第三步:通过状态的id来返回设置状态的方法,然后加入到设置方法的数组中

注意:自定义的渲染函数必须能够操作全局的状态id.

setters.push(createSetter(stateId));
function createSetter(stateId) {
return function (newState) {
states[stateId] = newState;
myRender();
}
}
function myRender() {
stateId = 0;
ReactDOM.render(<App />, document.querySelector('#root'));
}

第四步:返回第i个状态值和操作状态的方法,并对stateId+1

let value = states[stateId];
let setter = setters[stateId];
stateId++;
return [value, setter]

完整代码

import React from 'react'
import ReactDOM from 'react-dom'

// 自定义Hook

/**
* @description: 手写useState
* @param {*}
* @return {*}
*/
let states = [];
let setters = [];
let stateId = 0;
function createSetter(stateId) {
return function (newState) {
states[stateId] = newState;
myRender();
}
}
function myRender() {
stateId = 0;
ReactDOM.render(<App />, document.querySelector('#root'));
}
function myUseState(initialState) {
// 判断state是否存在,存在则使用以前的,不存在则使用初始值
states[stateId] = states[stateId] ? states[stateId] : initialState;
setters.push(createSetter(stateId));
let value = states[stateId];
let setter = setters[stateId];
stateId++;
return [value, setter]
}
function App() {
const [count, setCount] = myUseState(0);
const [name, setName] = myUseState('张三');
return (
<div>
<h1>当前求和为:{count}</h1>
<button onClick={() => setCount(count + 1)}>点我+1</button>
<h1>当前姓名为:{name}</h1>
<button onClick={() => setName('李四')}>点我切换姓名</button>
</div>
)
}

ReactDOM.render(<App />, document.querySelector('#root'));

CodeSandBox在线实现地址

参考资料


JustinHooks阅读需 2 分钟

什么是自定义Hook?

  • 自定义Hook是标准的封装和共享逻辑的方式。
  • 自定义Hook是一个函数,其名称以use开头。
  • 自定义Hook其实就是逻辑和内置的Hook的组合。

通俗的说,自定义Hook就是对组件间共用逻辑的封装,其名称以use开头。

实例一:封装共有请求

假设我们在组件挂载完成之后,需要对远程服务器发送一个GET请求,获取到文章数据,然后显示在页面中,我们可以通过下面的写法来完成,将获取数据折翼共有操作进行封装。

function useGetPost() {
const [post,setPost] = useState({});
useEffect(() => {
axios.get('https://jsonplaceholder.typicode.com/posts/1')
.then(res => setPost(res.data));
},[])
return [post,setPost];
}
function App() {
const [post] = useGetPost();
return (
<div>
<h1>{post.title}</h1>
<div>{post.body}</div>
</div>
)

}


### 实例二:封装输入框的value和onChange属性
>在Form表单中输入框的value和onChange属性是十分重要的,也是使用非常频繁的,如果针对每一个输入框都单独写onChange函数是非常臃肿的,通过自定义Hook进行封装则极大提高了便利性。

```js
// 定义自定义Hook:获取input的value和onChange
function useUpdateInput(initialValue) {
const [value,setValue] = useState(initialValue);
return {
value,
onChange: e => setValue(e.target.value)
}
}

function App() {
const usernameInput = useUpdateInput('');
const passwordInput = useUpdateInput('')
const submitForm = (event) => {
event.preventDefault();
console.log(usernameInput.value);
console.log(passwordInput.value);
}
return (
<form onSubmit={submitForm}>
<input type="text" name="username" {...usernameInput}/>
<input type="password" name="password" {...passwordInput} />
<input type="submit" />
</form>
)
}

什么是路由钩子函数?

React路由钩子函数是react-router-dom这个库提供的,主要提供了以下四个钩子函数:

  • useHistory
  • useLocation
  • useRouteMatch
  • useParams

注意:Link和Route必须在同一个路由器上,这个路由器可以是HashRouter也可以是BrowserRouter,Link to指示路由跳转到哪个组件,Route path 指示component

路由组件获取History、Location、Match与Params

这里要想获取到Params需要使用动态路由。

  • App组件
function App() {
return (
<div>
<div>
<Link to='/home/xiaoming'>首页</Link>
<Link to='/list'>列表页</Link>
</div>
<div>
<Route path='/home/:name' component={Home}/>
<Route path='/list' component={List}/>
</div>
</div>
)
}

ReactDOM.render(<Router>
<App />
</Router>, document.querySelector('#root'));
  • Home组件(路由组件)
import {useHistory,useLocation,useRouteMatch,useParams} from 'react-router-dom'
import React from 'react';

const Home = (props) => {
console.log(useHistory());
console.log(useLocation());
console.log(useRouteMatch());
console.log(useParams());
return (
<div>
这是Home组件
</div>
);
}

export default Home;

JustinHooks阅读需 2 分钟

什么是useMemo?

useMemo的行为类似Vue中的计算属性,可以检测某个值的变化,根据变化值计算新值。useMemo会缓存计算结果,如果检测值没有发生变化,即使组件重新渲染,也不会重新计算,此行为可以有助于避免在每个渲染上进行昂贵的计算。不要再useMemo函数中执行与渲染无关的操作。

useMemo的基本用法

function App() {

const [bool,setBool] = useState(true);
const [age,setAge] = useState('666');

const result = useMemo(() => {
console.log('检测到age发生变化');
return age * 2;
},[age])
return (
<div>
{age}
{bool ? '真': '假'}
<button onClick={() => setBool(!bool)}>点我切换布尔值</button>
<button onClick={() => setAge(age*1 + 1)}>点我age+1</button>
result是:{result}
</div>
)
}

memo方法

memo方法可以用于性能优化,如果本组件中的数据没有发生变化,阻止组件更新,类似类组件中的PureComponent和shouldComponentUpdate.

memo方法的基本用法

function App() {
const [count, setCount] = useState(0);
return (
<div>
<Foo />
<h1>当前求和为:{count}</h1>
<button onClick={() => setCount(count + 1)}>点我+1</button>
</div>
)

}

const Foo = memo(function Foo() {
console.log('Foo被渲染了');
return (
<div>这是Foo组件</div>
)
})

useCallback是什么?

常用于性能优化,缓存函数,使得组件重新渲染时得到相同的函数实例。

useCallback的基本用法

在这里我们需要注意的是useCallback的第一个参数时我们需要缓存的函数,第二个参数是一个数组,数组中包含的是没有变化的目标函数。

function App() {
const [count, setCount] = useState(0);
const resetCount = useCallback(() => setCount(0),[setCount]);
return (
<div>
<Foo resetCount={resetCount}/>
<h1>当前求和为:{count}</h1>
<button onClick={() => setCount(count + 1)}>点我+1</button>

</div>
)
}
const Foo = memo(function Foo(props) {
console.log('Foo被渲染了');
return (
<div>
这是Foo组件
<button onClick={props.resetCount}>点我归零</button>
</div>
)
})

参考资料


JustinHooks阅读需 2 分钟

什么是useEffect?

让函数型组件拥有处理副作用的能力,类似生命周期函数。

1. useEffect执行时机

可以把useEffect看做componentDidMount,componentDidUpdate,componentWillUnmount这三个函数的组合。

image.png

  • 当做componentDidMount和componentDidUpdate的时候
function App() {
const [count,setCount] = useState(0);
// 组件挂载完成之后 或 组件数据更新完成之后 执行
useEffect(() => {
console.log('组件挂载完成之后 或 组件数据更新完成之后 执行');
})
return (
<div>
{count}
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
)
}
  • 仅当做comonentDidMount的时候
useEffect(() => {
console.log('仅当做componentDidMount');
},[])
  • 当做componentWillunmount的时候(注意:这里不是仅当做componentWillunmount)
useEffect(() => () => {
console.log('当做componentWillUnmount');
})

2. useEffect的使用方法示例

  1. 为window对象添加滚动事件。(挂载完成后绑定事件,卸载组件后解除绑定)
function App() {
function onScroll() {
console.log('监听到页面发生滚动了');
}
useEffect(() => {
window.addEventListener('scroll',onScroll);
return () => {
// 卸载组件时解除对事件的绑定
window.removeEventListener('scroll',onScroll);
}
})
return (
<div>
App
</div>
)
}
  1. 设置定时器让count数值每隔一秒增加1。
function App() {

const [count,setCount] = useState(0);
useEffect(() => {
const timeId = setInterval(() => {
setCount(count => count + 1);
},1000)
return () => {
clearInterval(timeId);
}
},[])
return (
<div>
<h1>当前求和为:{count}</h1>
</div>
)
}

3. useEffect解决的问题

  1. 将一组相干的业务逻辑归置到了同一个副作用函数中.
  2. 简化重复代码,使组件内部代码更加清晰.

4:useEffect的第二个参数

  • 只有指定数据发生变化的时候才触发effect
useEffect(() => {
document.title = count;
}, [count])

5:useEffect结合异步函数

在useEffect中直接使用async和await是会报错的,推荐使用立即执行函数来包裹异步函数。

function getData() {
return new Promise(resolve => {
resolve({msg: 'Hello'})
})
}
function App() {
useEffect(() => {
(async function () {
const result = await getData();
console.log(result);
})()
},[])

return (
<div>
App
</div>
)

}


## 参考文献
* [官方文档](https://zh-hans.reactjs.org/docs/hooks-reference.html#useeffect)

JustinHooks阅读需 2 分钟

Hooks意为钩子,React Hooks就是一堆钩子函数,React通过这些钩子函数对函数型组件进行增强,不同的钩子函数提供了不同的功能。钩子函数有一个特点,都是以use开头。

为函数组件提供状态

useState函数的内部是使用闭包来实现函数保存状态数据的。

useState的返回值是什么?

返回值是一个数组,数组的第一项是状态数据,第二项是设置状态数据的方法,关于这个第二项的命名,我们一般采用set+状态数据名的方法,请看下面的例子。

const [count,setCount] = useState(0);

useState的细节

  1. 接收唯一的参数即状态初始值,初始值可以是任意数据类型。
  2. 返回值为数组,数组中存储状态值和更改状态值的方法,方法名称约定以set开头,后面加上状态名称。
  3. 方法可以被调用多次,用以保存不同的状态值。
  4. 参数可以是一个函数,函数返回什么,初始状态就是什么,函数只会被调用一次,用在初始值是动态值的情况。
function App(props) {
const [count,setCount] = useState(() => {
return props.count || 10;
});
const [person,setPerson] = useState({name: 'justin'})
return (
<div>
<span>{count},{person.name}</span>
<br />
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => setPerson({name: '国家'})}>点击切换name</button>
</div>
)
}

useState更新状态时是异步的

function App(props) {
const [count,setCount] = useState(0);
const [person,setPerson] = useState({name: 'justin'})
function handleCount() {
setCount(() => {
return count + 1;
})
console.log(count); // 打印的是0,说明是异步的
}
return (
<div>
<span>{count},{person.name}</span>
<br />
<button onClick={handleCount}>+1</button>
<button onClick={() => setPerson({name: '国家'})}>点击切换name</button>
</div>
)
}

读取state

在class的写法中,读取state需要使用this.state,但是在hook写法中,只需要直接使用状态的变量名即可。

为什么叫useState而不叫createState?

因为Create一词并不是很准确,因为state只在组件首次渲染的时候被创建,在下一次重新渲染的时候,useState返回给我们当前的state。


JustinHooks阅读需 2 分钟

Hook是React16.8的新增特性,它可以让我们不使用类组件的情况下使用state以及其他的React特性。

React Hooks介绍

React Hooks是用来做什么的?

对函数型组件进行增强,让函数型组件可以存储状态,可以拥有处理副作用的能力,让开发者在不使用类组件的情况下,实现相同的功能。

什么是副作用?

上文提到了副作用这一概念,下面我们来讲解下什么是副作用,我们只需要记住只要不是把数据转换为视图的代码那么就属于副作用,例如下面的副作用例子。在类组件中,我们通常使用生命周期钩子函数来处理这些副作用,但是在函数型组件中可以使用Hooks来帮助我们处理副作用

  • 获取DOM元素
  • 为DOM元素添加事件
  • 设置定时器
  • 发送ajax请求

类组件的不足(Hooks要解决的问题)

  1. 类组件缺少逻辑复用机制

    类组件一般采用高阶组件来复用逻辑,为了复用逻辑增加了无实际渲染效果的组件,增加了组件层级,显示十分臃肿,增加了调试的难度以及降低了运行效率。

  2. 类组件有时候很复杂难以维护

    有时候需要将一组相关的业务逻辑拆分到多个生命周期函数中,或者在一个生命周期函数内存在多个不相关的业务逻辑。

  3. 类成员方法不能保证this指向的正确性

    当我们给一个元素绑定事件的时候,在事件处理函数当中如果需要更改状态,通常需要更改这个函数的this指向,否则可能指向undefined.


JustinHooks阅读需 2 分钟