REACT V16.8 中加入了 React.FC 配合typescript 使用
一,函数式组件
- 纯函数
- 输入props,输出JSX
- 没有实例
- 没有生命周期
- 没有state
- 不能扩展其它方法
function List (props) {
const { text } = this.props
return (
<div>{text}</div>
)
}
无状态组件的性能比较高,因为它仅是一个函数,而普通组件是一个class
在越来越多的复杂的函数组件的情况下的时候;我们有时候需要的简单的组件;但是需要使用到一些他们的状态的保存和一些生命周期的比变化的 时候我们又不想去使用class的场景的组件;这时候需要怎么办呢;
于是乎在react的16.8中的时候react更新了关于react hooks 的一些东西;
简单的说就是在无状态组件中可以使用生命周期的一些方法;从而使得不需要很重量的代码就可以达到类中的状态管理
名称 | 解释 | 写法 | |
---|---|---|---|
useState | 用于为函数组件引入状态(state)。纯函数不能有状态,所以把状态放在钩子里面。 | const [on, setOn] = useState(false) 状态名改变状态的方法状态的默认值 | |
useContext() | 如果需要在组件之间共享状态 | ||
useReducer() action 钩子 |
React 本身不提供状态管理功能,通常需要使用外部库。这方面最常用的库是 Redux组件发出 action 与状态管理器通信。状态管理器收到 action 以后,使用 Reducer 函数算出新的状态,Reducer 函数的形式是(state, action) => newState | const [state, dispatch] = useReducer(reducer, initialState); | 上面是useReducer()的基本用法,它接受 Reducer 函数和状态的初始值作为参数,返回一个数组。数组的第一个成员是状态的当前值,第二个成员是发送 action 的dispatch函数。 |
useEffect()副作用钩子 | useEffect()用来引入具有副作用的操作,最常见的就是向服务器请求数据。以前,放在componentDidMount里面的代码,现在可以放在useEffect()。 | 每当组件参数personId发生变化,useEffect()就会执行。组件第一次渲染时,useEffect()也会执行。 | 上面用法中,useEffect()接受两个参数。第一个参数是一个函数,异步操作的代码放在里面。第二个参数是一个数组,用于给出 Effect 的依赖项,只要这个数组发生变化,useEffect()就会执行。第二个参数可以省略,这时每次组件渲染时,就会执行useEffect() |
useMemo() | 是定义一段函数逻辑是否重复执行 | 若第二个参数为空,则每次渲染组件该段逻辑都会被执行,就不会根据传入的属性值来判断逻辑是否重新执行,这样写useMemo()也就毫无意义。 | useMemo()是需要有返回值的,并且返回值是直接参与渲染,因此useMemo()是在渲染期间完成的。 |
useRef() | 是因为函数组件没有实例,所以函数组件中无法使用String Ref、Callback Ref、Create Ref,取而代之的是useRef 获取DOM元素的节点 获取子组件的实例 渲染周期之间共享数据的存储(state不能存储跨渲染周期的数据,因为state的保存会触发组件重渲染) |
||
useImperativeHandle | |||
useLayoutEffect | |||
useDebugValue |
useState explame
// useState explame
import React, { useState } from "react";
export default function Button() {
const [buttonText, setButtonText] = useState("Click me, please");
// 状态名 改变状态的方法 => 以及对应该状态名的默认的值
// 调用该状态改变的方法,赋值新的状态
function handleClick() {
return setButtonText("Thanks, been clicked!");
}
return <button onClick={handleClick}>{buttonText}</button>;
}
useContext() explame
import React from "react";
// 从react中获取对应的appcontent的组件
const {AppContext,Consumer} = React.createContext({});
constructor (props) {
super(props)
this.state = { theme: 'light' }
}
// AppContext.Provider 包裹在对应的函数中;对应的全局共享的字面量存储在value中
<AppContext.Provider value={{
username: 'superawesome'
}}>
<div className="App">
<Navbar/>
<Messages/>
</div>
</AppContext.Provider>
// 在对应的子组件中通过useContext(AppContext) 获取对应的值
const Navbar = () => {
const { username } = useContext(AppContext);
return (
<div className="navbar">
<p>AwesomeSite</p>
<p>{username}</p>
</div>
);
}
// 子组件中调用
import { Consumer } from "./index";//引入父组件的Consumer容器
render () {
return (
// Consumer 容器,可以拿到上文传递下来的 theme 属性,并可以展示对应的值
<Consumer>
{ theme => <div>子组件。获取父组件的值: {theme} </div> }
</Consumer>
)
}
useReducer() explame
import myReducer from ”Reducer“
function App() {
// 它接受 Reducer 函数和状态的初始值作为参数,
// 返回一个数组。数组的第一个成员是状态的当前值,第二个成员是发送 action 的dispatch函数。
const [state, dispatch] = useReducer(myReducer, { count: 0 });
return (
<div className="App">
<button onClick={() => dispatch({ type: 'countUp' })}>
+1
</button>
<p>Count: {state.count}</p>
</div>
);
}
useEffect() explame
上面用法中,useEffect()接受两个参数。第一个参数是一个函数,异步操作的代码放在里面。第二个参数是一个数组,用于给出 Effect 的依赖项,只要这个数组发生变化,useEffect()就会执行。第二个参数可以省略,这时每次组件渲染时,就会执行useEffect()
const Person = ({ personId }) => {
const [loading, setLoading] = useState(true);
const [person, setPerson] = useState({});
useEffect(() => {
setLoading(true);
fetch(`https://swapi.co/api/people/${personId}/`)
.then(response => response.json())
.then(data => {
setPerson(data);
setLoading(false);
});
}, [personId])
if (loading === true) {
return <p>Loading ...</p>
}
return <div>
<p>You're viewing: {person.name}</p>
<p>Height: {person.height}</p>
<p>Mass: {person.mass}</p>
</div>
}
useMemo() explame
若第二个参数为空,则每次渲染组件该段逻辑都会被执行,就不会根据传入的属性值来判断逻辑是否重新执行,这样写useMemo()也就毫无意义。
import React, { memo, useState, useMemo } from "react";
function App() {
const [value, setValue] = useState(0);
const increase = useMemo(() => {
if(value > 2) return value + 1;
}, [value]);
return (
<div>
<Child value={value} />
<button
type="button"
onClick={() => {
setValue(value + 1);
}}
>
value:{value},increase:{increase || 0}
</button>
</div>
);
}
// memo() 定义一个组件是否需要重复执行
const Child = memo(function Child(props) {
console.log('Child render')
return <h1>value:{props.value}</h1>;
});
export default App;
Refs
React 的核心思想是每次对于界面 state 的改动,都会重新渲染整个Virtual DOM,然后新老的两个 Virtual DOM 树进行 diff(协调算法),对比出变化的地方,然后通过 render 渲染到实际的UI界面,
使用 Refs 为我们提供了一种绕过状态更新和重新渲染时访问元素的方法;这在某些用例中很有用,但不应该作为 props
和 state
的替代方法。
在项目开发中,如果我们可以使用 声明式 或 提升 state 所在的组件层级(状态提升) 的方法来更新组件,最好不要使用 refs。
函数组件中 useRef() explame
import React, { useEffect, useRef } from 'react';
function App() {
const h1Ref = useRef();
useEffect(() => {
console.log('useRef')
console.log(h1Ref.current)
}, [])
return <h1 ref={h1Ref}>Hello World!</h1>
}
export default App;
管理焦点(如文本选择)或处理表单数据: Refs 将管理文本框当前焦点选中,或文本框其它属性。
在大多数情况下,我们推荐使用受控组件来处理表单数据。在一个受控组件中,表单数据是由 React 组件来管理的,每个状态更新都编写数据处理函数。另一种替代方案是使用非受控组件,这时表单数据将交由 DOM 节点来处理。要编写一个非受控组件,就需要使用 Refs 来从 DOM 节点中获取表单数据。
// 引出了非受控组件 获取dom节点
class NameForm extends React.Component {
constructor(props) {
super(props);
this.input = React.createRef();
}
handleSubmit = (e) => {
console.log('A name was submitted: ' + this.input.current.value);
e.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
- 因为非受控组件将真实数据储存在 DOM 节点中,所以在使用非受控组件时,有时候反而更容易同时集成 React 和非 React 代码。如果你不介意代码美观性,并且希望快速编写代码,使用非受控组件往往可以减少你的代码量。否则,你应该使用受控组件。
- 媒体播放:基于 React 的音乐或视频播放器可以利用 Refs 来管理其当前状态(播放/暂停),或管理播放进度等。这些更新不需要进行状态管理。
- 触发强制动画:如果要在元素上触发过强制动画时,可以使用 Refs 来执行此操作。threejs
- 集成第三方 DOM 库
import React from 'react';
// 不同于传递 createRef() 创建的 ref 属性,你会传递一个函数。
// 这个函数中接受 React 组件实例或 HTML DOM 元素作为参数,以使它们能在其他地方被存储和访问。
export default class Hello extends React.Component {
constructor(props) {
super(props);
this.textRef = null; // 创建 ref 为 null
}
componentDidMount() {
// 注意:这里没有使用 "current"
// 直接使用原生 API 使 text 输入框获得焦点
this.textRef.focus();
}
render() {
// 把 <input> ref 关联到构造器里创建的 textRef 上
return <input ref={node => this.textRef = node} />
}
}
React 将在组件挂载时将 DOM 元素传入ref
回调函数并调用,当卸载时传入 null
并调用它。在 componentDidMount
或 componentDidUpdate
触发前,React 会保证 refs 一定是最新的。
像上例, ref
回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null
,然后第二次会传入参数 DOM 元素。
这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。我们可以通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。
export default class Hello extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
// 通过 this.refs 调用
// 直接使用原生 API 使 text 输入框获得焦点
this.refs.textRef.focus();
}
render() {
// 把 <input> ref 关联到构造器里创建的 textRef 上
return <input ref='textRef' />
}
}
尽管字符串 stringRef 使用更方便,但是它有一些缺点,因此严格模式使用 stringRef 会报警告。官方推荐采用回调 Refs。
useRef()
比 ref
属性更有用。useRef()
Hook 不仅可以用于 DOM refs, useRef()
创建的 ref
对象是一个 current
属性可变且可以容纳任意值的通用容器,类似于一个 class 的实例属性。
function Timer() {
const intervalRef = useRef();
useEffect(() => {
const id = setInterval(() => {
// ...
});
intervalRef.current = id;
return () => {
clearInterval(intervalRef.current);
};
});
// ...
}
这是因为它创建的是一个普通 Javascript 对象。而 useRef() 和自建一个 {current: …}对象的唯一区别是,useRef 会在每次渲染时返回同一个 ref 对象。
请记住,当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现。
createRef 源码解析
// ReactCreateRef.js 文件
import type {RefObject} from 'shared/ReactTypes';
// an immutable object with a single mutable value
export function createRef(): RefObject {
const refObject = {
current: null,
};
if (__DEV__) {
// 封闭对象,阻止添加新属性并将所有现有属性标记为不可配置。当前属性的值只要可写就可以改变。
Object.seal(refObject);
}
return refObject;
}
...
export type RefObject = {
current: any,
};
forwardRef
在 React.createRef
中已经介绍过,有三种方式可以使用 React 元素的 ref
ref 是为了获取某个节点的实例,但是 函数式组件(PureComponent) 是没有实例的,不存在 this的,这种时候是拿不到函数式组件的 ref 的。
为了解决这个问题,由此引入 React.forwardRef
, **React.forwardRef**
允许某些组件接收 ref,并将其向下传递给 子组件
const ForwardInput = React.forwardRef((props, ref) => (
<input ref={ref} />
));
class TestComponent extends React.Component {
constructor(props) {
super(props);
this.inputRef = React.createRef(); // 创建 ref 存储 textRef DOM 元素
}
componentDidMount() {
this.inputRef.current.value = 'forwardRef'
}
render() {
return ( // 可以直接获取到 ForwardInput input 的 ref:
<ForwardInput ref={this.inputRef}>
)
}
}
- 只在使用
React.forwardRef
定义组件时,第二个参数 ref 才存在 - 在项目中组件库中尽量不要使用
React.forwardRef
,因为它可能会导致子组件被 破坏性更改 - 函数组件 和 class 组件均不接收
**ref**
参数 ,即 props 中不存在ref
,ref 必须独立 props 出来,否则会被 React 特殊处理掉。 - 通常在 高阶组件(HOC) 中使用
**React.forwardRef**
**<br />
function enhance(WrappedComponent) {
class Enhance extends React.Component {
componentWillReceiveProps(nextProps) {
console.log('Current props: ', this.props);
console.log('Next props: ', nextProps);
}
render() {
const {forwardedRef, ...others} = this.props;
// 将自定义的 prop 属性 “forwardedRef” 定义为 ref
return <WrappedComponent ref={forwardedRef} {...others} />;
}
}
// 注意 React.forwardRef 回调的第二个参数 “ref”。
// 我们可以将其作为常规 prop 属性传递给 Enhance,例如 “forwardedRef”
// 然后它就可以被挂载到被 Enhance 包裹的子组件上。
return React.forwardRef((props, ref) => {
return <Enhance {...props} forwardedRef={ref} />;
});
}
// 子组件
class MyComponent extends React.Component {
focus() {
// ...
}
// ...
}
// EnhancedComponent 会渲染一个高阶组件 enhance(MyComponent)
const EnhancedComponent = enhance(MyComponent);
const ref = React.createRef();
// 我们导入的 EnhancedComponent 组件是高阶组件(HOC)Enhance。
// 通过React.forwardRef 将 ref 将指向了 Enhance 内部的 MyComponent 组件
// 这意味着我们可以直接调用 ref.current.focus() 方法
<EnhancedComponent
label="Click Me"
handleClick={handleClick}
ref={ref}
/>;
forwardRef 与 Hook useImperativeHandle
useImperativeHandle
可以让你在使用 ref
时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle
应当与 forwardRef
一起使用:
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
在本例中,渲染 <FancyInput ref={fancyInputRef} />
的父组件可以调用 fancyInputRef.current.focus()
。
源码解读
export default function forwardRef<Props, ElementType: React$ElementType>(
render: (props: Props, ref: React$Ref<ElementType>) => React$Node,
) {
if (__DEV__) {
if (render != null && render.$$typeof === REACT_MEMO_TYPE) {
warningWithoutStack(
false,
'forwardRef requires a render function but received a `memo` ' +
'component. Instead of forwardRef(memo(...)), use ' +
'memo(forwardRef(...)).',
);
} else if (typeof render !== 'function') {
warningWithoutStack(
false,
'forwardRef requires a render function but was given %s.',
render === null ? 'null' : typeof render,