React基础入门

3,989 阅读13分钟

JSX

JSX 是在JavaScript 语法上的拓展,允许 HTML 代码和 JS 一起写。

///单行代码
const heading = <h1>Mozilla Developer Network</h1>;
///多行代码
const header = (
 <header>
   <h1>Mozilla Developer Network</h1>
 </header>
);
///heading/header 常量称为 JSX 表达式
///React 可以使header在我们的应用程序中进行渲染

JSX浏览器无法直接读取并解析,JSX表达式,经过parcelbabel编译后:

const header = React.createElement("header", null,
 React.createElement("h1", null, "Mozilla Developer Network")
);

实际开发中也可以跳过编译步骤,直接使用React.createElement()构建UI

创建React应用

npx包运行器;npm包管理器

创建react应用并启动

# 创建react应用模板
npx create-react-app react-demo
# 如果既有yarn又有npm,在创建时可以指定使用哪个创建
npx create-react-app react-demo --use-npm
#启动react应用
cd react-demo
#运行应用在http://localhost:3000
npm start

React中,组件是组成应用程序的可重复利用的模块。

组件 & Props

函数组件与class组件

// 函数组件
function TestComponent(props) {
 return <h1> Hi , {props.value} </h1>
}
///ES6 class组件
class TestComponent extends React.Component {
 render(){
  return <h1> Hi , {this.props.value} </h1>
}
}

注意: 组件名称必须以大写字母开头。小写字母开头的组件视为原生 DOM 标签

Props 的只读性

///纯函数,a、b的值不会被修改
function sum(a, b) {
 return a + b;
}

所有React 组件都必须像纯函数一样保护它们的 props 不被更改。

State&生命周期

///计时器组件
class Clock extends React.Component {
 ///构造函数
 constructor(props) {
   super(props)
   ///初始时间状态 & 计数器状态
   this.state = { 
     date: new Date(), 
     counter: 1 
  }
}
 ///返回组件的JSX
 render(){
   return (
     <div>
       <h1> 当前时间:{this.state.date.toLocaleTimeString()} </h1>
       <h2> 时间更新次数:{this.state.counter}</h2>
     </div>
  )
}
 ///生命周期函数
 ///组件已经被渲染到DOM中时调用
 componentDidMount() {
   const timerHandler = ()=>{
     ///错误示范
     // this.setState({
     //   counter: this.state.counter + this.props.increment,
     // });
     //联合更新,使用箭头函数,方式如下:
     this.setState((state,props)=>({
       date: new Date(),
       counter: state.counter +=  parseInt(props.increment)
    }))
     // 联合更新,使用普通函数,方式如下:
     this.setState(function(state,props){
       return {
         date: new Date(),
         counter: state.counter +=  parseInt(props.increment)
      }
    })
     ///简单更新
     this.setState({
       date: new Date()
    })

  }
   ///启动定时器
  this.timerId = setInterval(timerHandler,2000)
  console.log("componentDidMount")
}
 ///组件更新时调用
 componentDidUpdate(){
   console.log("componentDidUpdate")
}
 ///组件被销毁之前调用
 componentWillUnmount(){
   clearInterval(this.timerId)
   console.log("componentWillUnmount")
}
}

State 的更新可能是异步的,因此不能使用在setState中使用this.state。 组件可以选择把它的 state 作为 props向下传递到它的子组件中:

<FormattedDate date={this.state.date} />
function FormattedDate(props) {
  return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}

事件处理

  • React 事件的命名采用小驼峰式(camelCase),而不是纯小写
  • 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串
<--- 传统html --->
<button onclick="activateLasers()">
  Activate Lasers
</button>
<---React写法--->
<button onClick={activateLasers}>
  Activate Lasers
</button>

事件添加

///普通函数
class LoggingButton extends React.Component {
 constructor(){
   super()
   ///传建一个和原函数体相同的函数,这个函数和this进行绑定
   this.handleClick = this.handleClick.bind(this)
}
 // 若不绑定严格模式下,未bind,则this指向undifined
 handleClick(){
   console.log('this is:', this);
}
 render() {
   return (
     <button onClick={this.handleClick}>
      Click me
     </button>
      ///或者直接绑定
      <button onClick={handleClick.bind(this)}>
      Click me
     </button>
  );
}
}

///箭头函数
class LoggingButton extends React.Component {
 // 此语法确保 `handleClick` 内的 `this` 已被绑定。
 // 注意: 这是 *实验性* 语法。
 handleClick = () => {
   console.log('this is:', this);
}
 render() {
   return (
     <button onClick={this.handleClick}>
      Click me
     </button>
  );
}
}

向事件处理函数传参

///普通函数
handleClick(id){
   console.log('this is:', this);
   console.log(id)
}
<button onClick={this.handleClick.bind(this,2)}>
  Click me
</button>
///箭头函数
<button onClick={()=>{this.handleClick(2)}}>
Click me
</button>

条件渲染

元素变量

//声明一个变量并使用 if 语句进行条件渲染
render() {
   const isLoggedIn = this.state.isLoggedIn;
   let button;
   if (isLoggedIn) {
     button = <LogoutButton onClick={this.handleLogoutClick} />;
  } else {
     button = <LoginButton onClick={this.handleLoginClick} />;
  }
   return (
     <div>
       <Greeting isLoggedIn={isLoggedIn} />
      {button}
     </div>
  );
}

&&运算符

JavaScript 中,true && expression 总是会返回 expression, 而 false && expression 总是会返回 false。如果条件是 true&& 右侧的元素就会被渲染,如果是 falseReact 会忽略并跳过它。

render() {
  const count = 0;
  return (
    <div>
      {count && <h1>Messages: {count}</h1>}
    </div>
  );
}

三目运算符

render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      {isLoggedIn
        ? <LogoutButton onClick={this.handleLogoutClick} />
        : <LoginButton onClick={this.handleLoginClick} />
      }
    </div>
  );
}

阻止组件渲染

在极少数情况下,你可能希望能隐藏组件,即使它已经被其他组件渲染。若要完成此操作,你可以让 render 方法直接返回 null,而不进行任何渲染。

function WarningBanner(props) {
  if (!props.warn) {
    return null;
  }
  return (
    <div className="warning">
      Warning!
    </div>
  );
}
///其他组件引用`WarningBanner`,当warn为空,该组件不被渲染
 render() {
    return (
      <div>
        <WarningBanner warn={this.state.showWarning} />
        <button onClick={this.handleToggleClick}>
          {this.state.showWarning ? 'Hide' : 'Show'}
        </button>
      </div>
    );
  }

列表&Key

const root = ReactDOM.createRoot(document.getElementById('root'));
const numbers = [1, 2, 3, 4, 5];
root.render(
  <React.StrictMode>
   <List values={numbers} />
  </React.StrictMode>
);

function List(param) {
  ///集合的`Map`转换
  ///key 帮助 React 识别哪些元素改变了
  const listItem = param.values.map((num,index)=><li key={index} >{num}</li> )
  return <ul>{listItem}</ul>
  ///或者
  return <ul>{param.values.map((num,index)=><li key={index} >{num}</li> )}</ul>    
}

Key提取组件

元素的 key只有放在就近的数组上下文中才有意义。 比方说,如果你提取出一个 ListItem 组件,你应该把 key 保留在数组中的这个 <ListItem /> 元素上,而不是放在 ListItem 组件中的 <li> 元素上。 错误示例:

function ListItem(props) {
  const value = props.value;
  return (
    // 错误!你不需要在这里指定 key:
    <li key={value.toString()}>
      {value}
    </li>
  );
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // 错误!元素的 key 应该在这里指定:
    <ListItem value={number} />
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

正确示例:

///正确示例
function ListItem(props) {
  // 正确!这里不需要指定 key:
  return <li>{props.value}</li>;
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // 正确!key 应该在数组的上下文中被指定
    <ListItem key={number.toString()} value={number} />
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

组合&继承

组件使用一个特殊的 children 属性, 来将他们的子组件传递到组件中。

function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children}
    </div>
  );
}
function WelcomeDialog() {
  return (
    <FancyBorder color="blue"> 
      ///children
      <h1 className="Dialog-title">
        Welcome
      </h1>
    </FancyBorder>
  );
}

也可以自定义属性

function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.content}
    </div>
  );
}
function WelcomeDialog() {
  const content =  <h1 className="Dialog-title"> Welcome </h1>
  return (
    <FancyBorder color="blue" content={content} /> 
  );
}

代码分割

为了避免搞出大体积的代码包,需要进行代码分割。 代码分割是由诸如 WebpackRollup 和 Browserify(factor-bundle)这类打包器支持的一项技术,能够创建多个包并在运行时动态加载。

import()

///使用之前
import { add } from './math';
console.log(add(16, 26));
///使用之后
import("./math").then(math => {
  console.log(math.add(16, 26));
});

React.lazy

React.lazy 函数能让我们像渲染常规组件一样处理动态引入的组件。

///before
import OtherComponent from './OtherComponent';
///after
const OtherComponent = React.lazy(() => import('./OtherComponent'));

此代码将会在组件首次渲染时,自动导入包含 OtherComponent 组件的包。React.lazy 接受一个函数,这个函数需要动态调用 import()。它必须返回一个 Promise,该 Promise 需要 resolve 一个 default exportReact 组件。

import React, { Suspense } from 'react';

const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));

function MyComponent() {
  return (
    <div>
      //fallback 属性接受任何在组件加载过程中你想展示的 React 元素
      <Suspense fallback={<div>正在加载...</div>}>
        <section>
          <OtherComponent />
          <AnotherComponent />
        </section>
      </Suspense>
    </div>
  );
}

Context

Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。实现了一个组件树“全局”数据的共享。 通过挂载在class 上的 contextType 属性可以赋值为由 React.createContext() 创建的 Context 对象。此属性可以让你使用 this.context 来获取最近 Context 上的值。

///创建context并设置初始值
const ThemeContext = React.createContext('light')

export default class App extends React.Component {
  render() {
    return (
      ///ThemeContext.Provider 指定value修改初始值
      <ThemeContext.Provider value = 'dark'>
        <TabBar />
      </ThemeContext.Provider>
    )
  }
}
// 中间的组件不必指明往下传递 props
export class TabBar extends React.Component {
  
  render() {
    return <Navigation />
  }
}
/// 叶子节点直接获取
export class Navigation extends React.Component {
  ///必须以`contextType`进行指定,使得`this.context`能够获取
  static contextType = ThemeContext;
  
  render() {
    console.log(Navigation.contextType)
    return (
      <p> Naigation 的主题 : <span> {this.context} </span></p>
    )
  }
}

消耗多个context

// Theme context,默认的 theme 是 “light” 值
const ThemeContext = React.createContext('light');

// 用户登录 context
const UserContext = React.createContext({
  name: 'Guest',
});

class App extends React.Component {
  render() {
    const {signedInUser, theme} = this.props;

    // 提供初始 context 值的 App 组件
    return (
      <ThemeContext.Provider value={theme}>
        <UserContext.Provider value={signedInUser}>
          <Layout />
        </UserContext.Provider>
      </ThemeContext.Provider>
    );
  }
}

function Layout() {
  return (
    <div>
      <Sidebar />
      <Content />
    </div>
  );
}

// 一个组件可能会消费多个 context
function Content() {
  return (
    <ThemeContext.Consumer>
      {theme => (
        <UserContext.Consumer>
          {user => (
            <ProfilePage user={user} theme={theme} />
          )}
        </UserContext.Consumer>
      )}
    </ThemeContext.Consumer>
  );
}

错误边界

部分 UIJavaScript 错误不应该导致整个应用崩溃,为了解决这个问题,React 16 引入了一个新的概念 —— 错误边界。

错误边界是一种 React 组件,这种组件可以捕获发生在其子组件树任何位置的 JavaScript 错误,并打印这些错误,同时展示降级 UI,而并不会渲染那些发生崩溃的子组件树。错误边界可以捕获发生在整个子组件树的渲染期间生命周期方法以及构造函数中的错误。

注意:错误边界无法捕获以下场景中产生的错误:

  1. 事件处理
  2. 异步代码
  3. 服务端渲染
  4. 它自身抛出的错误

示例代码1:

//定义的错误边界组件
export default class ErrorBoundary extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            errorInfo: null,
            error: null
        }
    }
    componentDidCatch(err, errInfo) {
    // Catch errors in any components below and re-render with error message
        this.setState({
            error: err,
            errorInfo: errInfo,
        })
    // You can also log error messages to an error reporting service here
    }
    render() {
        if (this.state.errorInfo) {
            return (
                <div>
                    <h2> 出错了 </h2>
                    <p> {this.state.error && this.state.error.toString()} </p>
                    <p> {this.state.errorInfo && this.state.errorInfo.toString()} </p>
                </div>
            )
        }
        return this.props.children
    }
}
///使用函数组件
function Counter(params) {
  ///"React.useState" cannot be called in a class component,
  /// must be called in a React function component or a custom React Hook function
  const [count, setCount] = React.useState(0)
  function click1() {
    let x = count + 1
    setCount(x)
  }
  if (count === 2) {///模仿错误
    throw new Error("Crashed")
  }else{
    return <button onClick={click1}> {count} </button>
  }
}

示例代码2

///定义边界组件
export default class ErrorBoundary extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            hasError : false
        }
    }
    static getDerivedStateFromError(error) {
        return { hasError: true }
    }

    render() {
        if (this.state.hasError) {
            return <h2> 出错了 </h2>
        }
        return this.props.children
    }
}
///使用类组件
export default class Counter extends React.Component{
  constructor(props) {
    super(props);
    this.state = { counter:0 }
  }
  handleClick = ()=>{
    ///***********注意括号******
    this.setState(({counter}) => ({
      counter: counter + 1
    }));
  }
  render() { 
    return <button onClick={this.handleClick}> {this.state.counter} </button>
  }
}

最终使用

///代码分割,懒加载
const Error = React.lazy(() => import('./ErrorBoundary.jsx'))
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <p> text1 </p>
    ///与懒加载配合
    <Suspense fallback={<div>加载中...</div>} >
      <Error>
        <Counter />
      </Error>
    </Suspense>
    <p> text2 </p>
  </React.StrictMode>
);

注意:自 React 16 起,任何未被错误边界捕获的错误将会导致整个 React 组件树被卸载。

Refs

Refs 提供了一种方式,允许我们访问DOM节点或在 render 方法中创建的 React元素。

Refs 是使用 React.createRef() 创建的,并通过 ref 属性附加到 React 元素。在构造组件时,通常将 Refs分配给实例属性,以便可以在整个组件中引用它们。

///为DOM元素添加`Ref`
export default class RefComponent extends Component {
    constructor(props) {
        super(props);
        ///创建`Ref`
        this.pRef = React.createRef()
    }
    onClick = ()=>{
        const pNode  = this.pRef.current ///获取pNode
        pNode.textContent = '通过Refs操作添加的文本' ///操作元素
    }
    render() { 
        return (
        <div>
            <p ref={this.pRef}></p>
            <button onClick={
               this.onClick 
            } > 按钮 </button>
        </div>)
    }
}

React 会在组件挂载时给 current 属性传入 DOM元素,并在组件卸载时传入 null 值。ref 会在 componentDidMountcomponentDidUpdate 生命周期钩子触发前更新。

// 为`React`的class组件`RefComponent` 添加`ref`。
// 在组件加载后,通过该组件的`ref`,执行其内部的点击事件,触发组件内部<P>的更新
export class AutoRefComponet extends Component {
    constructor(props) {
        super(props);
        this.comRef = React.createRef()
    }
    componentDidMount(){
        this.comRef.current.onClick()
    }
    render() { 
        return <RefComponent ref={this.comRef}/>;
    }
}

AutoRefComponet通过Ref操作RefComponent组件,实现其内部P标签的更新,仅当RefComponent组件为Class组件时有效。

默认情况下,不能在函数组件上使用 ref 属性,但是可通过Refs转发或将函数组件转换为Class组件来使用Ref,前提条件这个ref只能指向一个DOM 元素或 class 组件。

///举例:函数组件内部使用`ref`,非属性
export default function RefComponent(){
    ///创建
    const pRef = useRef(null)

    function onClick(){
        const pNode  = pRef.current ///获取pNode
        pNode.textContent = '通过Refs操作添加的文本' ///操作元素
    }
    return (<div>
             <p ref={pRef}></p>
             <button onClick={onClick} > 按钮 </button>
           </div>
           )
}

Refs回调

另一种设置ref的方式,不同于传递 createRef() 创建的 ref 属性,需要传递一个函数。这个函数中接受 React 组件实例或 HTML DOM 元素作为参数,以使它们能在其他地方被存储和访问。

export default class RefComponent extends Component {
    constructor(props) {
        super(props);
        ///存储pnode
        this.pNode = null
        ///创建ref 回调
        this.pRef = (element) => {
            ///返回元素 dom or react 实例
            this.pNode = element
        }
    }
    onClick = ()=>{
        this.pNode.textContent = '通过Refs操作添加的文本' ///操作元素
    }
    render() { 
        return (
        <div>
            <p ref={this.pRef}></p>
            <button onClick={this.onClick} > 按钮 </button>
        </div>)
    }
}

React 将在组件挂载时,会调用 ref 回调函数并传入 DOM 元素,当卸载时调用它并传入 null

可以在组件间传递回调形式的 refs,就像你可以传递通过 React.createRef() 创建的对象 refs 一样。

export default class RefComponent extends Component {
    render() { 
        return (
        <div>
            <p ref={this.props.pref}></p>
            <button onClick={this.onClick} > 按钮 </button>
        </div>)
    }
}
// 为`React`的class组件,添加`ref`回调
export class AutoRefComponet extends Component {
    componentDidMount(){
        this.pNode.textContent = '通过Refs操作添加的文本'
    }
    render() { 
      /// 注意:不能是ref,换个名
        return <RefComponent pref={(element) => {
            this.pNode = element
        }}/>;
    }
}

Refs转发

修改一个子组件,需要使用新的 props 来重新渲染它,但是某些情况下需要强制修改,被修改的子组件可能是React组件或DOM元素。

比如上述的AutoRefComponet组件通过ref直接引用RefComponet组件,从而操作子组件, 16.3 或更高版本的 React,推荐使用Ref转发。 建议尽量使用状态提升来处理。

Ref 转发是一个可选特性,其允许某些组件接收 ref,并将其向下传递(换句话说,“转发”它)给子组件。

///普通的函数组件另一种定义形式
const RefComponent = (props) => (
    <div>
        <p> {props.value} </p>
    </div>
)
///ref转发示例
///子组件
const RefComponent = React.forwardRef((props, ref) => (
    <div>
        <p ref={ref}></p>
        <p> {props.value} </p>
    </div>)
)

// 父组件
export class AutoRefComponet extends Component {
    constructor(props) {
        super(props)
        this.getPref = React.createRef()
    }
    componentDidMount() {
        this.getPref.current.textContent = '通过Refs转发操作添加的文本'
    }
    render() {
        return <RefComponent ref = {this.getPref} value='匪夷所思的用法' />;
    }
}

Fragment

///横向列表显示 hello world 
<table>
  <tr>
    <td>Hello</td>
    <td>World</td>
  </tr>
</table>
///上述方式比较复杂,React提供了简单的方式
class Columns extends React.Component {
  render() {
    return (
      <React.Fragment>
        <td>Hello</td>
        <td>World</td>
      </React.Fragment>
    );
  }
}
///简写
class Columns extends React.Component {
  render() {
    return (
      <>
        <td>Hello</td>
        <td>World</td>
      </>
    );
  }
}

高阶组件

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。

高阶组件是参数为组件,返回值为新组件的函数。

const EnhancedComponent = higherOrderComponent(WrappedComponent);

详情

高阶组件

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。

高阶组件是参数为组件,返回值为新组件的函数。

const EnhancedComponent = higherOrderComponent(WrappedComponent);

详情

注意事项

  1. 不要在 render 方法中使用 HOC

如果从 render 返回的组件与前一个渲染中的组件相同(===),则 React 通过将子树与新子树进行区分来递归更新子树。 如果它们不相等,则完全卸载前一个子树。

render() {
  // 每次调用 render 函数都会创建一个新的 EnhancedComponent
  // EnhancedComponent1 !== EnhancedComponent2
  const EnhancedComponent = enhance(MyComponent);
  // 这将导致子树每次渲染都会进行卸载,和重新挂载的操作!
  return <EnhancedComponent />;
}

不仅仅出现性能问题 ,重新挂载组件会导致该组件及其所有子组件的状态丢失。

  1. 务必复制静态方法

React组件上定义静态方法很有用,但将 HOC 应用于组件时,原始组件将使用容器组件进行包装,新组件会没有原始组件的任何静态方法。

// 定义静态函数
WrappedComponent.staticMethod = function() {/*...*/}
// 现在使用 HOC
const EnhancedComponent = enhance(WrappedComponent);
// 增强组件没有 staticMethod
typeof EnhancedComponent.staticMethod === 'undefined' // true

解决办法

function enhance(WrappedComponent) {
  class Enhance extends React.Component {/*...*/}
  // 必须准确知道应该拷贝哪些方法 :(
  Enhance.staticMethod = WrappedComponent.staticMethod;
  return Enhance;
}

render props

render prop 是一个用于告知组件需要渲染什么内容的函数 prop

///图片组件,跟着鼠标移动
class Cat extends React.Component {
  render() {
    const mouse = this.props.mouse;
    return (
      <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
    );
  }
}

///鼠标移动的组件
class Mouse extends React.Component {
  constructor(props) {
    super(props);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.state = { x: 0, y: 0 };
  }

  handleMouseMove(event) {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }

  render() {
    return (
      <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>

        {/*
          使用 `render`prop 动态决定要渲染的内容,
          而不是给出一个 <Mouse> 渲染结果的静态表示
        */}
        {this.props.render(this.state)}
      </div>
    );
  }
}

class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>移动鼠标!</h1>
        ///告诉mouse组件需要渲染cat组件,也可以指定其他的组件
        <Mouse render={mouse => (
          <Cat mouse={mouse} />
        )}/>
      </div>
    );
  }
}

重要的是要记住,render prop 是因为模式才被称为 render prop ,你不一定要用名为 renderprop 来使用这种模式。事实上任何被用于告知组件需要渲染什么内容的函数 prop 在技术上都可以被称为 render prop

类型检查

const root = ReactDOM.createRoot(document.getElementById('root'));
const tmp = [12,33,2]
root.render(
  <React.StrictMode>
    <MyComponent name={tmp} />
  </React.StrictMode>
);
///定义
   import PropTypes from 'prop-types'
   export default class MyComponent extends Component {
    ///设置默认值
    static defaultProps = {
        name : "我的组件"
    }
    ///类型校验
    static propTypes = {
        name : PropTypes.string
    }
    constructor(props) {
        super(props);
    }
    render() { 
        return ( 
            <div>{this.props.name}</div>
         );
    }
}
///函数组件使用
import PropTypes from 'prop-types'

function HelloWorldComponent({ name }) {
  return (
    <div>Hello, {name}</div>
  )
}
///类型校验
HelloWorldComponent.propTypes = {
  name: PropTypes.string
}
 ///设置默认值
HelloWorldComponent.defaultProps = {
  name: "我的组件"
}
export default HelloWorldComponent

非受控组件

在 React 中,<input type="file" /> 始终是一个非受控组件,因为它的值只能由用户设置,而不能通过代码控制。

///通过DOM节点,读取数据
class FileInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.fileInput = React.createRef();
  }
  handleSubmit(event) {
    event.preventDefault();
    alert(
      `Selected file - ${this.fileInput.current.files[0].name}`
    );
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Upload file:
          <input type="file" ref={this.fileInput} />
        </label>
        <br />
        <button type="submit">Submit</button>
      </form>
    );
  }
}
ReactDOM.render(
  <FileInput />,
  document.getElementById('root')
);

HOOK

Hook React 16.8 的新增特性。它可以让你在不编写 class的情况下使用 state 以及其他的 React 特性

即,可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。

useState

关于函数组件

const Example = (props) => {
  return <div />;
}
///or
function Example(props) {
  return <div />;
}

函数组件中使用useState 会返回一对值:当前状态和更新它的函数,我们可以在事件处理函数中或其他一些地方调用这个函数,可重设状态。

///函数组件使用useState实现状态管理
///"React.useState" cannot be called in a class component,
/// must be called in a React function component or a custom React Hook function
function Counter(params) {
  /// 声明一个叫 “count” 的 state 变量
  const [count, setCount] = React.useState(0)
  function click1() {
    let x = count + 1
    ///状态更新
    setCount(x)
  }
   ///状态读取
   return <button onClick={click1}> {count} </button>
}

///等价的class组件
///使用类组件
export default class Counter extends React.Component{
  constructor(props) {
    super(props);
    this.state = { counter:0 }
  }
  handleClick = ()=>{
    ///***********注意括号******
    this.setState(({counter}) => ({
      counter: counter + 1
    }));
    //***或者可以这样***
    this.setState((state) => ({
      counter: state.counter + 1
    }));
  }
  render() { 
    return <button onClick={this.handleClick}> {this.state.counter} </button>
  }
}

惰性初始State

const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});

JavaScript解构赋值语法

详情

[a, b, ...rest] = [10, 20, 30, 40, 50];
console.log(a); // 10
console.log(b); // 20
console.log(rest); // [30, 40, 50]

({ a, b } = { a: 10, b: 20 });
console.log(a); // 10
console.log(b); // 20

({a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40});
console.log(a); // 10
console.log(b); // 20
console.log(rest); // {c: 30, d: 40}

///useState的语法 like this
function f() {
  return [1, 2];
}
let a, b;
[a, b] = f();
console.log(a); // 1
console.log(b); // 2

useEffect

useEffect 可看做 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合。具体定义:


type EffectCallback = () => (void | Destructor);
/*
@param effect 接收一个函数,该函数返回值为void,或返回一个 cleanup 函数
@param deps 可选参数,若有,effect函数仅在该列表中的值发生变化时,才会被执行
*/
function useEffect(effect: EffectCallback, deps?: DependencyList): void;

useEffect在第一次渲染之后和每次更新之后都会执行。React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。

function Counter(props) {
  const [count, setCount] = React.useState(0)
  function handleClick() {
    setCount(count + 1)
  }
  ///每次渲染都会执行effect, 每次effect都会清除上次的effect
  React.useEffect(()=>{
    document.title = `组件挂载|更新后,计数为:${count}`
    const cleanUp = ()=>{
      console.log(`组件卸载,计数为:${count}`);
    }
    return cleanUp
  },[count])
  return <button onClick={handleClick}> {count} </button>
}
///等价的class组件
export class Counter extends React.Component{
  constructor(props) {
    super(props);
    this.state = { count:0 }
  }
  componentDidMount(){
    document.title = `组件挂载后,计数为:${this.state.count}`
  }
  componentDidUpdate(prevProps,prevState) { ///点击触发
    if (prevState.count !== this.state.count) {
       document.title = `组件更新时,计数为${this.state.count}`
    }
  }
  componentWillUnmount(){
    document.title = `组件卸载时,计数为${this.state.count}`
  }
  handleClick = ()=>{
    this.setState((state) => ({
      count: state.count + 1
    }));
  }
  render() { 
    return <button onClick={this.handleClick}> {this.state.count} </button>
  }
}

多Effect

多个 Effect 实现关注点分离

function Form() {
  // 1. Use the name state variable
  const [name, setName] = useState('Mary');

  // 2. Use an effect for persisting the form
  useEffect(function persistForm() {
    localStorage.setItem('formData', name);
  });

  // 3. Use the surname state variable
  const [surname, setSurname] = useState('Poppins');

  // 4. Use an effect for updating the title
  useEffect(function updateTitle() {
    document.title = name + ' ' + surname;
  });
}

自定义HOOK

通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。

React 中有两种流行的方式来共享组件之间的状态逻辑: render props高阶组件

自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook

import { useState, useEffect } from 'react';

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    //通过friendID,查询是否在线
  });

  return isOnline;
}
///使用自定义的HOOK
function FriendListItem(props) {
  const isOnline = useFriendStatus(props.friend.id);
  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {props.friend.name}
    </li>
  );
}

更多HOOK API

参考资料

react.html.cn/docs/gettin…