REACT V16.8 中加入了 React.FC 配合typescript 使用
一,函数式组件
- 纯函数
- 输入props,输出JSX
- 没有实例
- 没有生命周期
- 没有state
- 不能扩展其它方法
function List (props) {const { text } = this.propsreturn (<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 explameimport 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} /><buttontype="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 valueexport 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” 定义为 refreturn <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() 方法<EnhancedComponentlabel="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,
