react base

924 阅读15分钟

一 react16的新特性有哪些?

* 1,类似try catch ErrorBoundary
   render(){
     return (
       <div>
         <ErrorBoundary>
           <Profile user={this.state.user} />
         </ErrorBoundary>
         <button onClick={this.onClick}>Update</button>
       </div>
     )
   }

2,render 在React 16中,render方法支持直接返回string,number,boolean,null,portal,以及fragments(带有key属性的数组)

    function Glossary(props) {
      return (
        <dl>
          {props.items.map(item => (
            <React.Fragment key={item.id}>
              <dt>{item.term}</dt>
              <dd>{item.description}</dd>
            </React.Fragment>
          ))}
        </dl>
      );
    }
export default function () {
    return [
        <div>一步 01</div>,
        <div>一步 02</div>,
        <div>一步 03</div>,
        <div>一步 04</div>
    ];
}

3,createPortal

* Portals机制提供了一种最直接的方式可以把一个子组件渲染到父组件渲染的DOM树之外 
* 通过使用createPortal,我们可以将组件渲染到我们想要的任意DOM节点中,但该组件依然处在React的父组件之内
* 在子组件产生的event依然可以被React父组件捕获,但在DOM结构中,它却不是你的父组件。对于组件组织,代码切割来说,这是一个很好的属性。
    //实现一个简易蒙层效果,抽象出一个通用的Overlay组件
    import React, { Component } from 'react';
    import ReactDOM from 'react-dom';
    
    export default class Overlay extends Component {
        constructor(props) {
            super(props);
            this.container = document.createElement('div');
            document.body.appendChild(this.container);
        }
        componentWillUnmount() {
            document.body.removeChild(this.container);
        }
        render() {
            return ReactDOM.createPortal(
                <div className='overlay'>
                    <span className='overlay-close' onClick={this.props.onClose}>&times;</span>
                    {this.props.children}
                </div>,
                this.container
            )
        }
    }
    //该组件对应的样式如下
    .overlay{
        box-sizing:border-box;
        position: fixed;
        top:50%;
        left:50%;
        width:260px;
        height:200px;
        margin-left:-130px;
        margin-top:-100px;
        padding:10px;
        background-color: #fff;
        outline: rgba(0,0,0,.5) solid 9999px;
    }
    .overlay-close{
        position: absolute;
        top:10px;
        right:10px;
        color:red;
        cursor: pointer;
    }
//使用如下:
class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      overlayActive: false
    }
    this.closeOverlay = this.closeOverlay.bind(this);
    this.showOverlay = this.showOverlay.bind(this);
  }
  closeOverlay() {
    this.setState({ overlayActive: false })
  }
  showOverlay() {
    this.setState({ overlayActive: true })
  }
  render() {
    return (
      <div className="App">
        <div>hello world!</div>
        {this.state.overlayActive &&
          <Overlay onClose={this.closeOverlay}>overlay content</Overlay>}
        <button onClick={this.showOverlay}>show</button>
      </div>
    );
  }
}

4,支持自定义DOM属性。在之前的版本中,React会忽略无法识别的HTML和SVG属性,自定义属性只能通过data-*形式添加,现在它会把这些属性直接传递给DOM(这个改动让React可以去掉属性白名单,从而减少了文件大小),不过有些写法仍然是无效的。如DOM传递的自定义属性是函数类型或event handler时,依然会被React忽略。

//错误写法
render(){
  return(
    <div a={()=>{}} onclick={this.showOverlay}></div>
  )
)
//Warning: Invalid event handler property `onclick`. Did you mean `onClick`?
//Warning: Invalid value for prop `a` on <div> tag. Either remove it from the element, or pass a string or number value to keep it in the DOM.

5,setState传入null时不会再触发更新。

* 比如在一个选择城市的函数中,当点击某个城市时,
newValue的值可能发生改变,也可能是点击了原来的城市,
值没有变化,返回null则可以直接避免触发更新,不会引起重复渲染
,不需要在shouldComponentUpdate函数里面去判断。
* 现在setState回调(第二个参数)
会在componentDidMount/componentDidUpdate后立即触发,而不是等到所有组件渲染完成之后。
    selectCity(e){
      const newValue = e.target.value;
      this.setState((state)=>{
        if(state.city===newValue){
          return null;
        }
        return {city:newValue}
      })
    )

6 更好的服务器端渲染

* React 16的SSR被完全重写,新的实现非常快,
接近3倍性能于React 15,现在提供一种流模式streaming,可以更快地把渲染的字节发送到客户端
* React 16在hydrating(注:指在客户端基于服务器返回的HTML再次重新渲染)方面也表现的更好,
React 16不再要求客户端的初始渲染完全和服务器返回的渲染结果一致,而是尽量重用已经存在的DOM元素
* 不会再有checksum(标记验证)!并对不一致发出警告。一般来说,在服务器和客户端渲染不同的内容是不建议的,但这样做在某些情况下也是有用的(比如,生成timestamp)。

* 体积变小,去掉了一些检查。
    * 新的打包策略中去掉了process.env检查。
    * React 16的体积比上个版本减小了32%(30% post-gzip),文件尺寸的减小一部分要归功于打包方法的改变。 
* 算法
     * 官方解释是“React Fiber是对核心算法的一次重新实现”,后续再深入学习

7 Fragment可以将一些子元素添加到 DOM tree 上且不需要为这些元素提供额外的父节点,相当于 render 返回数组元素

```
碎片;片段
render() {
      return (
        <Fragment>
          Some text.
          <h2>A heading</h2>
          More text.
          <h2>Another heading</h2>
          Even more text.
        </Fragment>
      );
    }
    可以简写 成 <></>, return 可以 不要括号
```

8 react-createRef

* 规范了 Ref 的获取方式,通过 React.createRef 取得 Ref 对象。
 ```
 // 16 -
      componentDidMount() {
        const el = this.refs.myRef
      }
    
      render() {
        return <div ref="myRef" />
      }
 // 16 +
   constructor(props) {
    super(props)
    
    this.myRef = React.createRef()
  }

  render() {
    return <div ref={this.myRef} />
  }
 ```

9 React.StrictMode

* 可以在开发阶段开启严格模式,发现应用存在的潜在问题,提升应用的健壮性,其主要能检测下列问题  
    * 识别被标志位不安全的生命周期函数
    * 对弃用的 API 进行警告
    * 探测某些产生副作用的方法
    * 检测是否使用 findDOMNode
    * 检测是否采用了老的 Context API 

10 生命周期函数的更新

* static getDerivedStateFromProps(nextProps, prevState)其作用是根据传递的 props 来更新 state
    * 它的一大特点是无副作用,由于处在 Render Phase 阶段,所以在每次的更新都会触发该函数, 在 API 设计上采用了静态方法,使其无法访问实例、无法通过 ref 访问到 DOM 对象等,保证了该函数的纯粹高效
* static getDerivedStateFromProps React v16.4 对她做了一些改变, 使其不仅在 props 更新时会被调用,setState 时也会被触发
```
static getDerivedStateFromProps(props, state) {
  if (props.value !== state.controlledValue) {
    return {
      controlledValue: props.value,
    };
  }
  
  return null;
}
```
* getSnapshotBeforeUpdate(prevProps, prevState)
    * getSnapshotBeforeUpdate(prevProps, prevState) 会在组件更新之前获取一个 snapshot,并可以将计算得的值或从 DOM 得到的信息传递到 componentDidUpdate(prevProps, prevState, snapshot) 函数的第三个参数,常常用于 scroll 位置定位等场景。
* componentDidCatch(error, info)
    * componentDidCatch 函数让开发者可以自主处理错误信息,诸如错误展示,上报错误等,用户可以创建自己的 Error Boundary 来捕获错误。

调用setState的时候实际发生了什么?

* 将新的setState参数对象合并到组件原先的state。
* 这回导致一个reconciliation调和的过程,这个‘调和的过程’就是尽可能的以最高效的方法基于新的state来更新UI。
* 会构建一个react元素树(ui对象),一旦构建完成,react会根据新的state去决定UI要进行怎么改变,找到树的新旧不同,react根据自身算法最高效的重新渲染。

setState第二个参数是什么,它有什么作用?

 * 一个可以在setState调用完成component重新渲染后被调用的回调函数,setState是异步操作函数,这也是它为什么把一个回调函数作为第二个参数的原因。虽然通常我更建议用一个生命周期函数去取代这个回调函数,但是知道这个东西的存在也不是什么坏事。

触发render的途径有哪些?

* 首次渲染
* this.setState。
* 父组件发生更新(props没变也render)
* this.forceUpdate(fn),不会经过shouldComponentUpdate。

refs是什么,还有为什么它很重要?

*  
```
// v15
this.refs.myInput.focus();
<input type="text" ref="myInput" />
```
```
// v16 这个方法不可以再纯函数上写,因为他们没有实例。
class MyComponent extends     React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }
    focusTextInput() {
    // 直接使用原生 API 使 text 输入框获得焦点
    // 注意:通过 "current" 取得 DOM 节点
    this.textInput.current.focus();
  }

  render() {
    return <div ref={this.textInput} />;
  }
}
```
```
// 纯函数如果要用refs需要这样写。
function CustomTextInput(props) {
  // 这里必须声明 textInput,这样 ref 回调才可以引用它
  let textInput = null;

  function handleClick() {
    textInput.focus();
  }

  return (
    <div>
      <input
        type="text"
        ref={(input) => { textInput = input; }} />

      <input
        type="button"
        value="Focus the text input"
        onClick={handleClick}
      />
    </div>
  );  
}
```

react路由如何做权限校验?

*  路由授权方案 
*  数据授权
*  操作授权
```
//routes.js
import { Icon } from 'assets'
import { WithAuthRouter } from '@components'
import Home from './home'
// 这里也可以用异步加载看个人喜好
const routes = [
  { path: '/Home', /
    exact: true,
    root: true,
    icon: Icon.COLLABORATION,
    title: '主页',
    component: WithAuthRouter(Home)
  }
  // WithAuthRouter.js
  import React from 'react'
    import {connect} from 'react-redux'
    import { compose, onlyUpdateForKeys, branch, renderComponent } from 'recompose'
    import Exception from 'ant-design-pro/lib/Exception'
    // 校验路由权限组件
    const isNotAuth = props => {
    // route 本路由表,user 全局登录信息、menu 服务端返回的菜单列表
      const { route: { path = null }, user = {}, menu = [] } = props
      const { userId = null, roleMap = [] } = user
      if (userId && roleMap.length) {
        const result = menu.find(i => i.menuUrl === path)
        if (result) {
          return !result.userIdList.includes(userId) && !result.userIdList.filter(i => roleMap.includes(i)).length > 0
        }
    
        return true
      }
    
      return true
    }
    
    const NotFound = () => (
      <Exception type='403' actions />
    )
    
    const renderWhileIsNotAuth = branch(
      isNotAuth,
      renderComponent(NotFound)
    )
    
    const mapState = state => ({
      user: state.user,
      menu: state.menu,
      inited: state.inited
    })
    
    const getUserAndMenu = compose(
      connect(mapState),
      onlyUpdateForKeys(['user', 'menu'])
    )
    
    export default compose(
      getUserAndMenu,
      renderWhileIsNotAuth
    )
```

React中的context的作用是什么?

TODO

redux 的适用场景是什么以及如何使用

  • redux是一个实现全局状态管理的工具。类比于vuex,主要的作用是用来给组件通讯的时候使用。使用场景就是当多个组件需要互通数据,而普通的兄弟组件父子组件通讯传值很麻烦,或者是多个组件需要同一数据但要载入时发多次的同一请求时,为了避免麻烦和提高用户体验,可以使用redux。
  • 什么场景下会默认使用React批量更新?
    • componentWillMount和React事件体系内 可解决性能问题。
  • 参考题库
  • 如何理解Redux?
    • React有props和state: props意味着父级分发下来的属性,state意味着组件内部可以自行管理的状态,并且整个React没有数据向上回溯的能力,也就是说数据只能单向向下分发,或者自行内部消化。理解这个是理解React和Redux的前提。
    • 一般构建的React组件内部可能是一个完整的应用,它自己工作良好,你可以通过属性作为API控制它。但是更多的时候发现React根本无法让两个组件互相交流,使用对方的数据。然后这时候不通过DOM沟通(也就是React体制内)解决的唯一办法就是提升state,将state放到共有的父组件中来管理,再作为props分发回子组件。
    • 子组件改变父组件state的办法只能是通过onClick触发父组件声明好的回调,也就是父组件提前声明好函数或方法作为契约描述自己的state将如何变化,再将它同样作为属性交给子组件使用。这样就出现了一个模式:数据总是单向从顶层向下分发的,但是只有子组件回调在概念上可以回到state顶层影响数据。这样state一定程度上是响应式的。
    • 为了面临所有可能的扩展问题,最容易想到的办法就是把所有state集中放到所有组件顶层,然后分发给所有组件。
    • 为了有更好的state管理,就需要一个库来作为更专业的顶层state分发给所有React应用,这就是Redux。让我们回来看看重现上面结构的需求:
      • a. 需要回调通知state (等同于回调参数) -> action
      • b. 需要根据回调处理 (等同于父级方法) -> reducer
      • c. 需要state (等同于总状态) -> store 对Redux来说只有这三个要素:
        • a.action是纯声明式的数据结构,只提供事件的所有要素,不提供逻辑。
        • b.reducer是一个匹配函数,action的发送是全局的:所有的reducer都可以捕捉到并匹配与自己相关与否,相关就拿走action中的要素进行逻辑处理,修改store中的状态,不相关就不对state做处理原样返回。
        • c. store负责存储状态并可以被react api回调,发布action.当然一般不会直接把两个库拿来用,还有一个binding叫react-redux, 提供一个Provider和connect。很多人其实看懂了redux卡在这里。
      • a. Provider是一个普通组件,可以作为顶层app的分发点,它只需要store属性就可以了。它会将state分发给所有被connect的组件,不管它在哪里,被嵌套多少层。
      • b. connect是真正的重点,它是一个科里化函数,意思是先接受两个参数(数据绑定mapStateToProps和事件绑定mapDispatchToProps),再接受一个参数(将要绑定的组件本身):
      • mapStateToProps:构建好Redux系统的时候,它会被自动初始化,但是你的React组件并不知道它的存在,因此你需要分拣出你需要的Redux状态,所以你需要绑定一个函数,它的参数是state,简单返回你关心的几个值。
      • mapDispatchToProps:声明好的action作为回调,也可以被注入到组件里,就是通过这个函数,它的参数是dispatch,通过redux的辅助方法bindActionCreator绑定所有action以及参数的dispatch,就可以作为属性在组件里面作为函数简单使用了,不需要手动dispatch。这个mapDispatchToProps是可选的,如果不传这个参数redux会简单把dispatch作为属性注入给组件,可以手动当做store.dispatch使用。这也是为什么要科里化的原因。
      • 做好以上流程Redux和React就可以工作了。简单地说就是:
        • 1.顶层分发状态,让React组件被动地渲染。
        • 2.监听事件,事件有权利回到所有状态顶层影响状态。

新语法:

  • react官方文档
  • 基础 Hook
    • useState
    const [state, setState] = useState(initialState);
    
    • useEffect
    useEffect(
      () => {
        const subscription = props.source.subscribe();
        return () => {
          subscription.unsubscribe();
        };
      },
      [props.source],
    );
    
    • useContext
    const themes = {
      light: {
        foreground: "#000000",
        background: "#eeeeee"
      },
      dark: {
        foreground: "#ffffff",
        background: "#222222"
      }
    };
    const ThemeContext = React.createContext(themes.light);
    function App() {
      return (
        <ThemeContext.Provider value={themes.dark}>
          <Toolbar />
        </ThemeContext.Provider>
      );
    }
    function Toolbar(props) {
      return (
        <div>
          <ThemedButton />
        </div>
      );
    }
    function ThemedButton() {
      const theme = useContext(ThemeContext);
      return (
        <button style={{ background: theme.background, color: theme.foreground }}>
          I am styled by theme context!
        </button>
      );
    }
    
  • 额外的 Hook
    • useReducer
      function init(initialCount) {
        return {count: initialCount};
      }
      function reducer(state, action) {
        switch (action.type) {
          case 'increment':
            return {count: state.count + 1};
          case 'decrement':
            return {count: state.count - 1};
          case 'reset':
            return init(action.payload);
          default:
            throw new Error();
        }
      }
      function Counter({initialCount}) {
        const [state, dispatch] = useReducer(reducer, initialCount, init);
        return (
          <>
            Count: {state.count}
            <button
              onClick={() => dispatch({type: 'reset', payload: initialCount})}>
              Reset
            </button>
            <button onClick={() => dispatch({type: 'decrement'})}>-</button>
            <button onClick={() => dispatch({type: 'increment'})}>+</button>
          </>
        );
      }
      
    • useCallback
    • useMemo
    • useRef
    • useImperativeHandle
    • useLayoutEffect
    • useDebugValue

hooks

* hooks是 解决了什么问题?
1、hooks是16.8版本引入的,允许你在不写class的情况下操作state 和react的其他特性。
  • 如何实现其中一个

react 一个生命周期的 原理如何实现的 ?

  • TODO

fiber(纤维)

  • 大家应该都清楚进程(Process)和线程(Thread)的概念,在计算机科学中还有一个概念叫做Fiber,英文含义就是“纤维”,意指比Thread更细的线,也就是比线程(Thread)控制得更精密的并发处理机制。
  • 上面说的Fiber和React Fiber不是相同的概念,但是,我相信,React团队把这个功能命名为Fiber,含义也是更加紧密的处理机制,比Thread更细。
  • React Fiber把更新过程碎片化,执行过程如下面的图所示,每执行完一段更新过程,就把控制权交还给React负责任务协调的模块,看看有没有其他紧急任务要做,如果没有就继续去更新,如果有紧急任务,那就去做紧急任务。 维护每一个分片的数据结构,就是Fiber。

react diff算法如何实现的 ?

  • TODO

生命周期

class A extends React.Component {
  // 用于初始化 state
  
  constructor() {}
  // 用于替换 `componentWillReceiveProps(nextProps)` ,该函数会在初始化和 `update` 时被调用
  // 因为该函数是静态函数,所以取不到 `this`
  // 如果需要对比 `prevProps` 需要单独在 `state` 中维护
  // 衍生 起源 React v16.3 静态方法里面的this为undefined
  static getDerivedStateFromProps(nextProps, prevState) {}
  // 判断是否需要更新组件,多用于组件性能优化
  shouldComponentUpdate(nextProps, nextState) {}
  // 组件挂载后调用
  // 可以在该函数中进行请求或者订阅
  componentDidMount() {}
  // 用于获得最新的 DOM 数据
  // 咬咬合射击被调用于render之后,可以读取但无法使用DOM的时候。它使您的组件可以在可能更改之前从DOM捕获一些信息(例如滚动位置)。此生命周期返回的任何值都将作为第三个 参数传递给componentDidUpdate()。
  // React v16.3
  getSnapshotBeforeUpdate() {}
  // 组件即将销毁
  // 可以在此处移除订阅,定时器等等
  componentWillUnmount() {}
  // 组件销毁后调用
  componentDidUnMount() {}
  // 组件更新后调用
  此方法在组件更新后被调用,可以操作组件更新的DOM,prevProps和prevState这两个参数指的是组件更新前的props和state
  componentDidUpdate(nextProps, nextState) {}
  // 渲染组件函数
  render() {}
  // 以下函数不建议使用
  UNSAFE_componentWillMount() {}  17版废弃
  UNSAFE_componentWillUpdate(nextProps, nextState) {}  17版废弃
  在该函数(componentWillReceiveProps)中调用 this.setState()   将不会引起第二次渲染
  UNSAFE_componentWillReceiveProps(nextProps) {} 17版废弃
}
this.forceUpdate(fn)  // 强制更新 不会通过不会经过shouldComponentUpdate。
componentDidCatch 函数让开发者可以自主处理错误信息,诸如错误展示,上报错误等,用户可
static getDerivedStateFromError: 此生命周期会在渲染阶段后代组件抛出错误后被调用。 它将抛出的错误作为参数,并返回一个值以更新 state。