py-tofee / Notes

2 stars 0 forks source link

React #20

Open py-tofee opened 3 years ago

py-tofee commented 3 years ago

事件处理

  1. 小驼峰命名,例如:
    <button onClick={handleClick}></button>
  2. 阻止默认行为,必须显示的调用preventDefault
    function handleClick(e) {
    // react中的e是合成事件,不需要我们担心跨浏览器兼容的问题
    e.preventDefault()
    //...
    }
  3. this绑定
    class MyComponent extends React.Component {
    constructor(props) {
    super(props)
    this.state = {
      isToggleOn: true
    }
    // 方法一:手动绑定
    this.handleClick = this.handleClick.bind(this)
    }
    // class中的方法默认不会绑定this
    handleClick() {
    this.setState((state) => ({
      isToggleOn: !state.isToggleOn
    }))
    }
    // 方法二:使用public class fields语法
    handleClick = () => {
    this.setState((state) => ({
      isToggleOn: !state.isToggleOn
    }))
    }
    render() {
    return (
      // 如果直接使用没有绑定this的handleClick,handleClick中的this为undefined
      <button onClick={this.handleClick}></button>
      // 方法三:此方法在每次渲染(调用render函数)的时候,都会创建一个新的函数,如果被当做prop传给子组件,子组件可能会进行额外的渲染,容易导致性能问题
      <myButton onClick={() => this.handleClick()}></myButton>
    )
    }
    }
  4. 参数传递
    // 事件对象e通常作为函数的第二个参数传递
    // 箭头函数中的e需要显示的传递
    <button onClick={(e) => this.handleClick(id, e)}></button>
    // bind方式,e被隐式的传递,不应该在jsx中使用bind,bind也会返回一个新函数
    <button onClick={this.handleClick.bind(this, id)}></button>

条件渲染

  1. if语句
  2. 在jsx中,通过花括号{}包裹代码,可以在里面嵌入任何表达式:
    • &&
    • 三目运算
    • render函数返回null阻止组件渲染

列表 & key

  1. key不会当做prop传入子组件,只有react内部才会使用到key

表单

  1. 受控组件:指被react控制取值和输入的表单元素,react中state是唯一数据源,并且只能通过setState更新
    <input type="text" value={this.state.value} onChange={this.handleChange} />
    <select value={this.state.value} onChange={this.handleChange}>
    <option>...</option>
    </select>
    // 多选,value为数组
    <select multiple={true} value={['a', 'b']} onChange={this.handleChange}>
    <option>...</option>
    </select>
  2. 非受控组件
    // 允许用户从本地选择一个或者多个文件,value是只读的,且只能有用户主动发起读取文件的行为
    <input type="file" />
  3. 处理表单:Formik

状态提升

  1. 多个子组件状态需要共享时,可以将状态提升至共同的父组件中

组合

  1. 特殊的prop:children,类似vue的默认插槽
  2. react组件的prop可以是任意数据,包含事件,react元素等
  3. 组合:将组件A写入组件B的内容中,B包裹A,B将组件A的内容通过prop.children传入,类似vue插槽
  4. 使用场景:底层组件需要从顶层组件获取数据,中间层次不关心这些数据,为了避免需要一层层传递这些数据,将底层组件放在顶层组件中创建,使用顶层组件的作用域,并把底层组件当做prop传递下去
    function Page(props) {
    const userLink = (
    <Link href={user.permalink}>
      <Avatar user={props.user} size={props.avatarSize} />
    </Link>
    )
    return <PageLayout userLink={userLink} />
    }
    // 现在,我们有这样的组件:
    <Page user={user} avatarSize={avatarSize} />
    // ... 渲染出 ...
    <PageLayout userLink={...} />
    // ... 渲染出 ...
    <NavigationBar userLink={...} />
    // ... 渲染出 ...
    {props.userLink}

Context 组件之间共享

向当前组件树下的所有子组件“广播”需要共享的数据,例如local, theme或者一些缓存的用户信息等

  1. Context.Provider

    // 只有当所处组件树中没有匹配到Provider时,defaultValue才会生效
    const MyContext = React.createContext(defaultValue);
    // 每个context对象都会返回一个Provider React组件,它允许消费组件订阅context的变化
    // 一个Provider组件可以对应多个消费组件,多个Provider组件可以嵌套使用,里层的数据会覆盖外层的数据
    // 当Provider的value值发生变化,它内部的所有消费组件都会重新渲染,且不受shouldComponentUpdate函数的影响
    <MyContext.Provider value={/* 某个值 */}>
  2. Context.Consumer - class

    class MyClass extends React.Component {
    // class的静态属性contextType会被赋值为一个Context对象
    // 组件内部可以通过this.context来消费最近Context对象上的值
    // this.context可以在任意生命周期中访问到,render函数中也能访问到
    static contextType = MyContext;
    render() {
    let value = this.context;
    /* 基于这个值进行渲染工作 */
    }
    }
  3. Context.Consumer - functional

    function MyClass() {
    return (<MyContext.Consumer>
    {value => {
    // 基于context的值进行渲染
    }}
    </MyContext.Consumer>)
    }
  4. Context.displayName 定义在DevTools中如何显示创建的context

    
    const MyContext = React.createContext(defaultValue)
    MyContext.displayName = 'MyDisplayName'

// 在DevTools中显示为 MyDisplayName.Provider // 在DevTools中显示为 MyDisplayName.Consumer


5. 动态Context
```js
toggleTheme() {
  // 修改state.theme
}
<ThemeContext.Provider value={this.state.theme}>
  <Toolbar changeTheme={this.toggleTheme} />
</ThemeContext.Provider>

也可以将更新context的函数放在context中,Consumer组件可以直接调用Provider传进来的context中的函数,更新context的值, 这样嵌套层级深的组件也可以方便的修改context值

export const ThemeContext = React.createContext({
  theme: themes.dark,
  toggleTheme: () => {/*...*/}
})

嵌套使用Provider和Consumer,可以实现一个组件消费多个context

错误边界组件

如果一个class组件中,定义了static getDirevedStateFromError()或者componentDidCatch中的任意一个或者两个,那么它就变成一个 错误边界组件,错误边界组件只能捕获其子组件的错误,不能捕获自身的错误;错误边界组件可以嵌套使用,如果一个错误边界组件无法渲染 错误信息,那么它会冒泡至最近的上层错误边界组件去处理。 错误边界只能处理渲染期间的错误,不能捕获事件处理器内部的错误,因为事件处理器不会在渲染期间触发,比如点击事件等。

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      hasError: false
    }
  }

  static getDirevedStateFromError(error) {
    // 更新 state 使下一次渲染能够显示降级后的UI
    return { hasError: true }
  }
  componentDidCatch(error, errorInfo) {
    // 可以在这里将错误日志上报给服务器
    logErrorToService(error, errorInfo)
  }

  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>
    }
    return this.props.childen
  }
}
// 使用
<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

错误追踪

在开发环境中,React 16会把渲染期间发生的所有错误打印到控制台,可以定位到具体某个组件(文件名)的错误, 在babel配置中添加插件 babel-plugin-transform-react-jsx-source,可以定位到具体行号的错误信息 使用create-react-app创建,默认是开启错误信息可追踪到文件名和行号的。

Refs转发

React ref 用于实现对组件实例的引用 或者 指向具体的DOM元素 使用React.createRef()创建React ref,只有React.forwardRef()定义的组件才能接受ref参数, 普通函数组件和class组件 不接受 ref参数

const FancyButton = React.forwardRef((props, ref) => (
  // ref转发到DOM元素上
  <button ref={ref} className="fancy-button">{props.children}</button>
))

父组件

const ref = React.createRef()
// 向下传递ref
<FancyButton ref={ref}>click me</FancyButton>

在高阶组件中转发ref

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

function logProps(wrappedComponent) {
  class LogProps extends React.Component {
    constructor(props) {
      super(props)
    }
    render() {
      const {forwardedRef, ...rest} = this.props
      return <wrappedComponent ref={forwardedRef} {...rest}></wrappedComponent>
    }
  }
  return React.forwardRef((props, ref) => {
    // 将ref作为props属性forwardedRef传递下去
    return <LogProps {...props} forwardedRef={ref} />
  })
}

// FancyButton.js
class FancyButton extends React.Component {
  // ...
}
export default logProps(FancyButton)

// 父组件
import FancyButton from './FancyButton'
const ref = React.createRef()

<FancyButton ref={ref} label="click me" handleClick={handleClick} />

ref

Fragments

类似vue中的