React基础

129 阅读6分钟

1.目标

  1. 入门React,了解常规的用法
  2. 掌握面试中React的基础问题
  3. 掌握React学习路线

2.知识要点

初始化一个react app

  1. 确保你安装了较新版本的 Node.js
  2. 按照 Create React App 安装指南创建一个新的项目
npx create-react-app my-app

2.1 React简介

React 是⼀个声明式,⾼效且灵活的⽤于构建⽤户界⾯的 JavaScript 库

2.2 JSX模板语法

JSX称为JS的语法扩展,ui与logic层的结合,使用{}标识

使用小驼峰命名

  • class -> className
  • table->tableIndex

用法

//变量
const name ='lance'
const element = <h1>{name}</h1>;
​
//调用方法
function getName(){
    return 'lance'
}
const element = (<h1>{getName()}</h1>)
                      
//表达式
 const getGreeting = function(){
    return <h1>{getName()}</h1>
}        
​
//指定属性
const user = {
    name:'lance'
}
const elememt =<img src={user.name}>
​
    //表示对象
   const element = (
  <h1 className="greeting">
   Hello, world!
  </h1>
);
// 等同于React.createElement
const element = React.createElement(
  'h1',
 {className: 'greeting'},
  'Hello, world!'
);
 
    //更新元素 也使⽤ ReactDOM.render,react只更新实际改变的内容
    function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
 );
  ReactDOM.render(element, document.getElementById('root'));
}

JSX转JS

通过babel来转换JSX

可以在babel官⽹中尝试,babeljs.io/repl 可以使⽤官⽹提供的create-react-app npm run eject 来看babelrc中的配置,www.babeljs.cn/docs/babel-…

//安装babel及react 的依赖
npm install core-js @babel/core @babel/preset-env @babel/preset-react @babel/register babel-loader @babel/plugin-transform-runtime --save-dev
​

.babelrc 文件

{
    "presets":[
        "@babel/preset-env",
        "@babel/preset-react"
    ],
    "plugins":[
        "@babel/plugin-transform-runtime"
    ]
}

2.3 props, state

props用于父组件向子组件传值不可被修改,state是当前组件的数据状态,可以出发组件重新渲染

  • 函数式组件
  • Class类组件
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}
class Welcome extends React.Component {
    constructor(props){
        super(props);
        this.state={message:'hello'};
        //这一行很重要
        this.handleClick = this.handleClick.bind(this);
    }
    handleClick(){
        console.log(this.state.message)
    }
  render() {
    <button onClick={this.handleClick}>say hello</button>
 }
}

2.4 渲染组件

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;
ReactDOM.render(
  element,
  document.getElementById('root')
);
// ⾃定义组件使⽤⼤写字⺟开头
import React from 'react';
// 正确!组件需要以⼤写字⺟开头:
function Hello(props) {
  // 正确! 这种 <div> 的使⽤是合法的,因为 div 是⼀个有效的 HTML 标签:
  return <div>Hello {props.toWhat}</div>;
}
function HelloWorld() {
  // 正确!React 知道 <Hello /> 是⼀个组件,因为它是⼤写字⺟开头的:
  return <Hello toWhat="World" />;
}
​

2.5 调用生命周期钩子

class Welcome extends React.Component {
    constructor(props){
        super(props);
        this.state={message:'hello'};
        //这一行很重要
        this.handleClick = this.handleClick.bind(this);
    }
    handleClick(){
        console.log(this.state.message)
    }
    componentDidMount(){
     this.setState({
     message:'hello'
     })
      this.timerID = setInterval(
     () => this.tick(),
      1000
   );
    }
    componentWillUnmount(){
     clearInterval(this.timerID);
    }
    
  render() {
    <button onClick={this.handleClick}>say hello</button>
 }
}

2.6 state

  • 构造函数是唯⼀可以给state赋值的地⽅
  • state更新可能是‘异步’的,异步⽬的:batch 处理,性能优化
  • 单向数据流,只在当前的组件⾥⽣效,属于组件内的属性,重复实例化相同的组件,内部的内存地址也是 不⼀样的
  • setState 只在合成事件和钩⼦函数中是“异步”的,在原⽣事件和 setTimeout 中都是 同步的;
  • setState的“异步”并不是说内部由异步代码实现,其实本身执⾏的过程和代码都是同步 的, 只是合成事件和钩⼦函数的调⽤顺序在更新之前,导致在合成事件和钩⼦函数中没法⽴⻢拿到 更新后的值,形式了所谓的“异步”,当然可以通过第⼆个参数 setState(partialState, callback) 中的callback拿到更 新后的结果。
  • setState 的批量更新优化也是建⽴在“异步”(合成事件、钩⼦函数)之上的,在原⽣事 件和setTimeout 中不会批量更新,在“异步”中如果对同⼀个值进⾏多次 setState , setState 的批量更新策略会对其进⾏覆盖,取最后⼀次的执⾏,如果是同时 setState 多个不 同的值,在更新时会对其进⾏合并批量更新。
class Welcome extends React.Component {
    constructor(props){
        super(props);
        this.state={message:'hello',counter:1};
        //这一行很重要
        this.handleClick = this.handleClick.bind(this);
    }
    handleClick(){
       this.setState(function(state, props) {
  return {
    counter: state.counter + props.increment
 };
});
    }
    componentDidMount(){
     this.setState({
     message:'hello'
     })
      this.timerID = setInterval(
     () => this.tick(),
      1000
   );
    }
    componentWillUnmount(){
     clearInterval(this.timerID);
    }
    
  render() {
    <button onClick={this.handleClick}>say hello</button>
 }
}
// setState 异步
// 异步⽬的:batch 处理,性能优化
1. 合成事件
class App extends Component {
  state = { val: 0 }
  increment = () => {
    this.setState({ val: this.state.val + 1 })
    console.log(this.state.val) // 输出的是更新前的val --> 0
 }
  render() {
    return (
      <div onClick={this.increment}>
       {`Counter is: ${this.state.val}`}
      </div>
   )
 }
}
2. ⽣命周期
class App extends Component {
  state = { val: 0 }
componentDidMount() {
    this.setState({ val: this.state.val + 1 })
   console.log(this.state.val) // 输出的还是更新前的值 --> 0
 }
  render() {
    return (
      <div>
       {`Counter is: ${this.state.val}`}
      </div>
   )
 }
}
3. 原⽣事件
class App extends Component {
  state = { val: 0 }
  changeValue = () => {
    this.setState({ val: this.state.val + 1 })
    console.log(this.state.val) // 输出的是更新后的值 --> 1
 }
componentDidMount() {
    document.body.addEventListener('click', this.changeValue, false)
 }
  render() {
    return (
      <div>
       {`Counter is: ${this.state.val}`}
      </div>
   )
 }
}
4. setTimeout
class App extends Component {
  state = { val: 0 }
componentDidMount() {
    setTimeout(_ => {
      this.setState({ val: this.state.val + 1 })
      console.log(this.state.val) // 输出更新后的值 --> 1
   }, 0)
 }
  render() {
    return (
      <div>
       {`Counter is: ${this.state.val}`}
      </div>
   )
 }
}
​
5. 批处理
class App extends Component {
  state = { val: 0 }
  batchUpdates = () => {
    this.setState({ val: this.state.val + 1 })
    this.setState({ val: this.state.val + 1 })
    this.setState({ val: this.state.val + 1 })
 }
render() {
    return (
      <div onClick={this.batchUpdates}>
       {`Counter is ${this.state.val}`} // 1
      </div>
   )
 }
}
​
​

3. 生命周期

3.1.1 componentDidMount

组件挂载后,调用

import * as React from 'react';
class Lifehooks extends React.Component {
  constructor (props) {
    super();
    this.state = {name: 'lance'};
    this.handleClick = this.handleClick.bind(this);
  }
​
  handleClick () {
    alert('hi react');
  }
​
  componentDidMount () {
    console.log('hi react');
  }
​
  render () {
    return (
      <div>
      <h1>
        hi {this.state.name}
      </h1>
    <button onClick={this.handleClick}>click me</button>
      </div>
  )
  }
}
​
export default Lifehooks;

3.1.2 componentDidUpdate

更新后会被立即调用,首次渲染不会执行此方法

componentDidUpdate(prevProps, prevState, snapshot)

componentDidUpdate(prevProps) {
  // 典型⽤法(不要忘记⽐较 props):加条件判断,不然死循环
  if (this.props.userID !== prevProps.userID) {
    this.fetchData(this.props.userID);
 }
}
如果组件实现了 getSnapshotBeforeUpdate() ⽣命周期,
则它的返回值将作为 componentDidUpdate() 的第三个参数 “snapshot” 参数传递。否则此
参数将为 undefined。

3.1.3 componentWillUnmount

componentWillUnmount() 会在组件卸载及销毁之前直接调⽤。例如,清除 timer,取消⽹络请求; componentWillUnmount() 中不应调⽤ setState(),因为该组件将永远不会重新渲染;

3.1.4 shouldComponentUpdate(nextProps,nextState)

通过判断nextProps和nextState的值,返回false,可以控制render(), componentDidUpdate()不调用,

import * as React from 'react';
import {SearchBar} from './SearchBar';
import {ProductTable} from './ProductTable';
import {DATA} from './data';
import _ from 'lodash';
​
export class FilterableProductTable extends React.Component {
  constructor (props) {
    super(props);
    this.handleOnSearchChange = this.handleOnSearchChange.bind(this);
    this.data = FilterableProductTable.buildData();
    this.state = {
      showData: this.data,
    };
  }
​
  static buildData () {
    const data = DATA;
    _.forEach(data, item => {
      item['searchKey'] = `${item.name}${item.price}`;
    });
    return data;
  }
​
  handleOnSearchChange (value) {
    const filterData = _.filter(this.data, item => {
      return item.searchKey.includes(value);
    });
    this.setState(state => ({
      showData: filterData,
    }));
  }
​
  render () {
    return (
      <div>
        <SearchBar onSearchChange={this.handleOnSearchChange}></SearchBar>
        <ProductTable products={this.state.showData}></ProductTable>
      </div>
    );
  }
}

3.1.5 getDerivedStateFromProps(nextProps,preState)

根据props的变化改变state

  • 在使用此生命周期时,要注意nextProps和preState进行比较
  • 静态方法,保持它是纯函数,不要产生副作用

3.1.6 getSnapshotBeforeUpdate(prevProps,prevState)

getSnapshotBeforeUpdate() 在最近⼀次渲染输出(提交到 DOM 节点)之前调⽤; 此⽣命周期⽅法的任何返回值将作为参数传递给 componentDidUpdate()。

import * as React from 'react';
​
export class ScrollList extends React.Component {
  constructor (props) {
    super(props);
    this.state = {
      list: this.props.list,
    };
    this.listRef = React.createRef();
  }
​
  getSnapshotBeforeUpdate (prevProps, prevState) {
    //比较list是否有更新
    /// 我们是否在 list 中添加新的 items ?
    //     // 捕获滚动••位置以便我们稍后调整滚动位置。
    if (prevProps.list.length < this.props.list.length) {
      const list = this.listRef.current;
      return list.scrollHeight - list.scrollTop;
    }
    return null;
  }
​
  componentDidUpdate (prevProps, prevState, snapshot) {
    if (snapshot != null) {
      // 如果我们 snapshot 有值,说明我们刚刚添加了新的 items,
      // 调整滚动位置使得这些新 items 不会将旧的 items 推出视图。
      //(这⾥的 snapshot 是 getSnapshotBeforeUpdate 的返回值)
      const list = this.listRef.current;
      list.scrollTop = list.scrollHeight - snapshot;
    }
  }
​
  render () {
    return (
      <div ref={this.listRef} style={{
        height: '300px',
        overflowY: 'auto',
      }}>
        {
          this.state.list.map(item => {
            return <div style={{height: '100px'}} key={item + 1}>{item}</div>;
          })
        }
      </div>
    );
  }
}
​

3.1.7 static getDerivedStateFromError

此⽣命周期会在后代组件抛出错误后被调⽤。 它将抛出的错误作为参数,并返回⼀个值以更新 state;

3.1.8 componentDidCatch(error,info)

componentDidCatch() 会在“提交”阶段被调⽤,因此允许执⾏副作⽤。 它应该⽤于记录错误之类的情 况;

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
 }
  static getDerivedStateFromError(error) {
    // 更新 state 使下⼀次渲染可以显示降级 UI
    return { hasError: true };
 }
  componentDidCatch(error, info) {
    // "组件堆栈" 例⼦:
    //   in ComponentThatThrows (created by App)
    //   in ErrorBoundary (created by App)
    //   in div (created by App)
    //   in App
    logComponentStackToMyService(info.componentStack);
 }
  render() {
    if (this.state.hasError) {
      // 你可以渲染任何⾃定义的降级 UI
      return <h1>Something went wrong.</h1>;
   }
    return this.props.children;
 }
}
​

4.事件处理

  • 在JSX元素上添加事件,通过on*EventType这种内联⽅式添加,命名采⽤⼩驼峰式(camelCase)的形 式,⽽不是纯⼩写(原⽣HTML中对DOM元素绑定事件,事件类型是⼩写的);
  • ⽆需调⽤addEventListener进⾏事件监听,也⽆需考虑兼容性,React已经封装好了⼀些的事件类 型属性;
  • 使⽤ JSX 语法时你需要传⼊⼀个函数作为事件处理函数,⽽不是⼀个字符串;
  • 不能通过返回 false 的⽅式阻⽌默认⾏为。你必须显式的使⽤ preventDefault;
⼀般不需要使⽤ addEventListener 为已创建的 DOM 元素添加监听器;
function Form() {
  function handleSubmit(e) {
    e.preventDefault();
    console.log('You clicked submit.');
 }
  return (
    <form onSubmit={handleSubmit}>
      <button type="submit">Submit</button>
    </form>
 );
}
class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};
    // 为了在回调中使⽤ `this`,这个绑定是必不可少的
    this.handleClick = this.handleClick.bind(this);
 }
  handleClick() {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
   }));
 }
  render() {
    return (
      // class 的⽅法默认不会绑定 this。如果没有绑定 this.handleClick 并把它传⼊
了 onClick,
      // this 的值为 undefined。
      <button onClick={this.handleClick}>
       {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
   );
 }
}
2. 箭头函数,问题: 每次render都会创建不同的回调函数,如果该回调函数作为props传⼊⼦组
件,每次⼦组件都要re-render
class LoggingButton extends React.Component {
  handleClick() {
    console.log('this is:', this);
 }
  render() {
    // 此语法确保 `handleClick` 内的 `this` 已被绑定。
    return (
      <button onClick={() => this.handleClick()}>
 //  <button onClick={this.handleClick().bind(this)}>
       Click me
      </button>
   );
 }
}
​

4.1接收参数

  • 事件对象 e 会被作为第⼆个参数传递;
  • 通过箭头函数的⽅式,事件对象必须显式的进⾏传递;
  • 通过 Function.prototype.bind 的⽅式,事件对象以及更多的参数将会被隐式的进⾏传递;
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

5. 条件渲染

class LoginControl extends React.Component {
  constructor(props) {
    super(props);
    this.handleLoginClick = this.handleLoginClick.bind(this);
    this.handleLogoutClick = this.handleLogoutClick.bind(this);
    this.state = {isLoggedIn: false};
 }
  handleLoginClick() {
    this.setState({isLoggedIn: true});
 }
  handleLogoutClick() {
    this.setState({isLoggedIn: false});
 }
  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>
   );
 }
}
​

5.1 与运算符 &&

function Mailbox(props) {
  const unreadMessages = props.unreadMessages;
  return (
    <div>
      <h1>Hello!</h1>
     {unreadMessages.length > 0 &&
        <h2>
         You have {unreadMessages.length} unread messages.
        </h2>
     }
    </div>
 );
}

5.2 三元运算符

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

5.3如何阻⽌组件渲染

function WarningBanner(props) {
//返回null,不渲染
    if (!props.warn) {
    return null;
 }
  return (
    <div className="warning">
     Warning!
    </div>
 );
}

5.4 列表

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    <li key={number.toString()}>
     {number}
    </li>
 );
  return (
    <ul>{listItems}</ul>
 );
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);
// 若没有key,会warning a key should be provided for list items
// key可以帮助react diff,最好不⽤index作为key,会导致性能变差;
// 如果不指定显式的 key 值,默认使⽤索引⽤作为列表项⽬的 key 值;