React入门简单食用

286 阅读14分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情

本文适用于react15+

React是一个只专注于界面渲染的前端框架,他只做一件事,就是更好的渲染数据。数据流的处理和状态的维护需要开发者来控制,使用相对比较灵活。


1. Hello React:第三方库引入版

经过如下的操作,可以实现在一个单HTML文件中食用React,完全离线的前提是你要下载好相应的js文件

<!-- 准备好一个“容器” -->
<div id="test"></div>

<!-- 库下载到本地 (完全离线开发) -->
<!-- 引入react核心库 -->
<!-- <script type="text/javascript" src="../js/react.development.js"></script> -->
<!-- 引入react-dom,用于支持react操作DOM -->
<!-- <script type="text/javascript" src="../js/react-dom.development.js"></script> -->
<!-- 引入babel,用于将jsx转为js -->
<!-- <script type="text/javascript" src="../js/babel.min.js"></script> -->

<!-- 不想下载到本地的话,当然也可使用cdn -->
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script crossorigin="anonymous" integrity="sha384-Fw04PJPC6ayYz0V6lPpRaiQxouxskHt/AjlelA2NWboTMnn5niXpXMyN4hftijud" src="https://lib.baomitu.com/babel-core/5.8.38/browser.min.js"></script>

<script type="text/babel" > /* 此处一定要写babel */
    //1.创建虚拟DOM
    const VDOM = () => <h1>Hello,React</h1> /* 此处一定不要写引号,因为不是字符串 */
    
    //2.渲染虚拟DOM到页面(React17及以下)
    // ReactDOM.render(<VDOM />, document.getElementById('test'))
    
    //3.React18写法(使用上边的CDN即可):
    ReactDOM.createRoot(document.getElementById('test')).render(
        <React.StrictMode>
          <VDOM />
        </React.StrictMode>
    )
    
    // 当然也可以完全不用组建的思想,这样声明:
    // const VDOM = <h1>Hello,React</h1> 
    // ReactDOM.render(VDOM, document.getElementById('test'))
</script>

CDN地址可在React和Babel官网获取

直接在浏览器打开这个HTML:

image.png

2. 试着丰富自己的VDOM

我们可以再改改上面的const VDOM,使自己的html更复杂一些

const VDOM = () => (  /* 此处一定不要写引号,因为不是字符串 */
  <h1 id="title">
    <span>Hello,React</span>
  </h1>
)

当然了,创建VDOM也可以不使用jsx,react提供了相应的API

//1.创建虚拟DOM
const VDOM = () => React.createElement('h1', {id:'title'}, React.createElement('span',{},'Hello,React'))

PS. 使用react自带的api相对繁琐,不利于节点嵌套书写,没有jsx直观

关于虚拟DOM:

1. 本质是Object类型的对象(一般对象)
2. 虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在用,无需真实DOM上那么多的属性。
3. 虚拟DOM最终会被React转化为真实DOM,呈现在页面上。

3. 上手jsx

jsx语法规则:

  1. 定义虚拟DOM时,不要写引号。
  2. 标签中混入JS表达式时要用{}
  3. 样式的类名指定不要用class,要用className
  4. 内联样式,要用style={{ key: value }}的形式去写。
  5. 只有一个根标签
  6. 标签必须闭合
  7. 标签首字母规范
    (1). 若小写字母开头,则将该标签转为html中同名元素,若html中无该标签对应的同名元素,则报错。 (2). 若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。
  8. 循环表达式中,每一项应加入唯一标识的key

下面给一写规范定义的例子:

// 一定注意区分:【js语句(代码)】与【js表达式】
// 1.表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
//       下面这些都是表达式:
//           (1). a
//           (2). a+b
//           (3). demo(1)
//           (4). arr.map() 
//           (5). function test () {}
// 2.语句(代码):
//       下面这些都是语句(代码):
//           (1).if(){}
//           (2).for(){}
//           (3).switch(){case:xxxx}

//模拟一些数据
const data = ['Angular','React','Vue']
//1.创建虚拟DOM
const VDOM = () => (
  <div>
    <h1>前端js框架列表</h1>
    <ul>
      {
        data.map((item,index)=>{
          return <li key={index}>{item}</li>
        })
      }
    </ul>
  </div>
)
//2.渲染虚拟DOM到页面 (以React17为例)
ReactDOM.render(<VDOM />, document.getElementById('test'))

在上边的例子中,使用一个{}的代码块包裹一个循环的js表达式,来达到循环渲染的效果。

4. 组件的类别

函数式

//1. 创建函数式组件

// 写法1
function MyComponent() {
  console.log(this); //此处的this是undefined,因为babel编译后开启了严格模式
  return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
}

// 写法2
const MyComponent = () => {
  return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
}

//2.渲染组件到页面(以React17为例)
ReactDOM.render(<MyComponent/>, document.getElementById('test'))

执行了ReactDOM.render(.......之后,发生了什么?

  1. React解析组件标签,找到了 MyComponent 组件。
  2. 发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。

分析:

  • 优点:使用纯函数,更轻量,无需创建类实例,开销小。便于组件分装和高阶组件使用。推荐使用,hooks全支持!
  • 缺点:没有this,没有状态(有了hooks后可以解决)。

类式

//2. 创建类式组件
class MyComponent extends React.Component {
  render(){
    //render是放在哪里的?—— MyComponent的原型对象上,供实例使用。调用次数1+N
    //render中的this是谁?—— MyComponent的实例对象 <=> MyComponent组件实例对象。
    console.log('render中的this:',this);
    return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
  }
}
//2.渲染组件到页面(React17为例)
ReactDOM.render(<MyComponent/>,document.getElementById('test'))

render调用次数为1+N,指的是初始加载时render一次,当每次setState时,便更新一次render函数。函数式组件中其实也是这样,每一次状态更新都会触发整个函数的重渲染,函数式组件可以使用useMemouseCallback等钩子来做优化。

与函数式对比:

  • 类式虽然可以用this,但是类组件中的this是可以改变的,某些情况下增大了空指针的概率。
  • 类式组件体量更加臃肿,不容易拆分。
  • 函数式组件更灵活,优化方法和可扩展性更强。
  • 新版本功能不在倾向于支持类式组件了。

5. 组件实例三大属性 state、props、ref

state组件状态,React.Component自带属性

// state类式使用,函数式使用见hooks
//初始化状态
state = {isHot:false,wind:'微风'}

render(){
  const {isHot,wind} = this.state
  return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
}

//自定义方法————要用赋值语句的形式+箭头函数
changeWeather = ()=>{
  const isHot = this.state.isHot
  this.setState({isHot:!isHot})
}

hooks使用:

const [isHot, setIsHot] = useState(false);

props父子传参

// 1.props类式使用
//创建组件
class Person extends React.Component{
  render(){
    const {name,age,sex} = this.props
    return (
      <ul>
        <li>姓名:{name}</li>
        <li>性别:{sex}</li>
        <li>年龄:{age+1}</li>
      </ul>
    )
  }
}
//渲染组件到页面
ReactDOM.render(<Person name="jerry" age={19}  sex="男"/>,document.getElementById('test1'))
ReactDOM.render(<Person name="tom" age={18} sex="女"/>,document.getElementById('test2'))

const p = {name:'老刘',age:18,sex:'女'}
// console.log('@',...p);
// ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>,document.getElementById('test3'))
ReactDOM.render(<Person {...p}/>,document.getElementById('test3'))

// 2.参数类型限制
//对标签属性进行类型、必要性的限制
// npm使用时,可安装 prop-types 包
<script type="text/javascript" src="../js/prop-types.js"></script>

Person.propTypes = {
  name:PropTypes.string.isRequired, //限制name必传,且为字符串
  sex:PropTypes.string,//限制sex为字符串
  age:PropTypes.number,//限制age为数值
  speak:PropTypes.func,//限制speak为函数
}
//指定默认标签属性值
Person.defaultProps = {
  sex:'男',//sex默认值为男
  age:18 //age默认值为18
}

// 3. 函数式组件使用
//创建组件
function Person (props){
  const {name,age,sex} = props
  return (
      <ul>
        <li>姓名:{name}</li>
        <li>性别:{sex}</li>
        <li>年龄:{age}</li>
      </ul>
    )
}

父组件也可以给子组件传递一个回调事件,在子组件中调用该事件便可实现子向父传递。

refs

// ref类式使用,函数式使用见hooks
//创建组件
class Demo extends React.Component{
  //展示左侧输入框的数据
  showData = (e) => {
    const {input1} = this.refs
    alert(input1.value)
  }
  //展示右侧输入框的数据
  showData2 = (e) => {
    const {input2} = this.refs
    alert(input2.value)
    alert(this.myRef.current.value);
  }
  // 使用API
  myRef = React.createRef()

  render(){
    return(
      <div>
        <input ref="input1" type="text" placeholder="点击按钮提示数据"/>&nbsp;
        <button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
            <input onBlur={this.showData2} ref={c => this.input2 = c } type="text" placeholder="失去焦点提示数据"/>&nbsp;
            <input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>&nbsp;
      </div>
    )
  }
}

hooks使用:

const Foo = React.forwardRef((props, myRef) => {
  const [value, setValue] = useState(0);

  useImperativeHandle(ref, () => ({
    // 暴露数据
    getValue: () => value
  }));
  
  return (
      <input type="text" value={value} onChange={e => setValue(e.target.value)}/>
  );
});

// 使用
const fooRef = useRef();

// 查看ref中暴露出来的值
console.log(fooRef.current.getValue())
...
<Foo ref={fooRef} />

6. 处理表单

  1. 非受控表单
//创建组件
class Login extends React.Component {
  handleSubmit = (event) => {
    event.preventDefault() //阻止表单提交
    const {username,password} = this
    alert(`你输入的用户名是:${username.value},你输入的密码是:${password.value}`)
  }
  render(){
    return(
      <form onSubmit={this.handleSubmit}>
        用户名:<input ref={c => this.username = c} type="text" name="username"/>
        密码:<input ref={c => this.password = c} type="password" name="password"/>
        <button>登录</button>
      </form>
    )
  }
}
  1. 受控表单
//创建组件
class Login extends React.Component {

  //初始化状态
  state = {
    username:'', //用户名
    password:'' //密码
  }

  //保存用户名到状态中
  saveUsername = (event) => {
    this.setState({username:event.target.value})
  }

  //保存密码到状态中
  savePassword = (event) => {
    this.setState({password:event.target.value})
  }

  //表单提交的回调
  handleSubmit = (event) => {
    event.preventDefault() //阻止表单提交
    const {username,password} = this.state
    alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
  }

  render(){
    return(
      <form onSubmit={this.handleSubmit}>
        用户名:<input onChange={this.saveUsername} type="text" name="username"/>
        密码:<input onChange={this.savePassword} type="password" name="password"/>
        <button>登录</button>
      </form>
    )
  }
}

处理表单中,类式组件和函数式并无新的区别

7. 高阶函数使用


// 渲染一个自定义表单

// 一级组件
function FormItem ({ field }) {
    
    const filterItem = () => ({
        Input: <input field={field} type="input" {...props} />,
        InputPassword: (
            <input field={field} type="password" {...props} />
        ),
        TextArea: <textarea field={field} {...props} />,
        Select: <select field={field}>{
            field.options?.map(op => <option key={op.value} value={op.value}>{op.label}</option>)
        }</select>,
        Date: <input field={field} type="date" {...props} />,
        Content: field.content,
    });
    
    return <div clasName="form-item">
        {filterItem[field.type]}
    </div>
}

// 二级组件
function Form({ items }) {
    return <>
        {items.map((item, index) => <FromItem key={index} field={item}>)}
    </>
}

// 三级使用
function App() {
    const items = [
        { type: 'Input', onChange: () => {} },
        { type: 'Select', options: [{ value: 1, label: 'apple' }, { value: 2, label: '包娜娜' }] }
    ];

    return <Form items={items} />
}

8. 新旧生命周期

  1. v16之前版本

react生命周期(旧).png

简介:

  • componentWillReceiveProps在props变化之前触发,hooks中可使用useEffect代替
  • shouldComponentUpdate用于判断是否需要更新组件,在初始化加载或者setState时触发。是一种性能优化钩子,hooks中,可使用useMemouseCallback等代替。
  • componentWillUpdate在组件即将变化前触发,尽量避免使用,在这个钩子中若修改了状态值,会造成渲染死循环。hooks中可使用useEffect代替。
  • componentDidUpdate在组件更新之后调用,尽量避免使用,在这个钩子中若修改了状态值,会造成渲染死循环。
  • componentWillMount在组件加载之前调用,在v17中已经移除。若v16中出现警告,可以使用UNSAFE_componentWillMount代替。
  • componentDidMount会在组件挂载后(插入 DOM 树中)立即触发。依赖于 DOM 节点的初始化操作应该放在这里。如需通过网络请求获取数据,也可以写在这里。hooks中可使用useEffect代替
  • componentWillUnmount在组件销毁之前触发,hooks中可使用useEffect代替
  1. v16+

对比于 React 15 废弃了 componentWillMount ,新增了 getDerivedStateFromProps

getDerivedStateFromProps 的设计初衷是替换 componentWillReceiveProps ,它有且仅有一个作用:让组件在 props 变化时派生/更新 state

react生命周期(新).png

9. 开始使用hooks

hooks,翻译过来还是钩子,字面意思跟React生命周期钩子是一个意思,至少是为了完成同样的任务才造出来的。所以就可以这样理解,hooks就是给函数式组件量身定做的,用于取代类式组件生命周期钩子的一类组件API。

与类式组件对比

参照组:

class Demo extends React.Component {

  // 1
  state = {count:0}

  // 2
  myRef = React.createRef()
  add = () => {
      this.setState(state => ({count:state.count+1}))
  }

  unmount = () => {
      ReactDOM.unmountComponentAtNode(document.getElementById('root'))
  }

  show = () => {
    alert(this.myRef.current.value)
  }

  // 3
  componentDidMount() {
    this.timer = setInterval(() => {
      this.setState( state => ({count:state.count+1}))
    }, 1000)
  }

  // 4
  componentWillUnmount() {
    clearInterval(this.timer)
  }

  render() {
    return (
        <div>
            <input type="text" ref={this.myRef}/>
            <h2>当前求和为{this.state.count}</h2>
            <button onClick={this.add}>点我+1</button>
            <button onClick={this.unmount}>卸载组件</button>
            <button onClick={this.show}>点击提示数据</button>
        </div>
    )
  }
}

上才艺:

function Demo(){
  // 1.state
  const [count,setCount] = React.useState(0)
  // 2.ref
  const myRef = React.useRef()

  // 3
  React.useEffect(() => {
    let timer = setInterval(() => {
        setCount(count => count + 1 )
    },1000)
    return () => {
      // 4. unmount
      clearInterval(timer)
    }
  },[])

  //加的回调
  function add(){
      // setCount(count + 1) //第一种写法
      setCount(count => count + 1 )
  }

  //提示输入的回调
  function show() {
      alert(myRef.current.value)
  }

  //卸载组件的回调
  function unmount() {
      ReactDOM.unmountComponentAtNode(document.getElementById('root'))
  }

return (
    <div>
        <input type="text" ref={myRef}/>
        <h2>当前求和为:{count}</h2>
        <button onClick={add}>点我+1</button>
        <button onClick={unmount}>卸载组件</button>
        <button onClick={show}>点我提示数据</button>
    </div>
  )
}

export default Demo

hooks详细

React Hooks就是加强版的函数组件,可以完全不使用 class,就能写出一个全功能的组件,没有了继承,没有了渲染逻辑,没有了生命周期。

  1. userState 状态钩子

他将一个普通变量暂存在react里,使得组件更新时刷新函数后,这个值仍能保持原来的状态。

// 计数器
const AddCount = () => {
  const [ count, setCount ] = useState(0)
  const addcount = () => {
    let newCount = count
    setCount(newCount+=1)
  } 
  return (
    <>
      <p>{count}</p>
      <button onClick={addcount}>count++</button>
    </>
  )
}
  1. useContext 共享状态钩子

共享全局状态,下面的 AppContext 可以作为单例单独配置。需要使用生产者消费者模式,往context里provide数据。

const Ceshi = () => {
  // 可提出来作为单独模块 
  const AppContext = React.createContext({})
  const A = () => {
    const { name } = useContext(AppContext)
    return (
        <p>我是A组件的名字{name}<span>我是A的子组件{name}</span></p>
    )
  }
  const B =() => {
    const { name } = useContext(AppContext)
    return (
        <p>我是B组件的名字{name}</p>
    )
  }
  return (
    <AppContext.Provider value={{name: 'hook测试'}}>
      <A/>
      <B/>
    </AppContext.Provider>
  )
}
  1. useReducer Action钩子

和redux一样,需要通过页面组件发起action来调用reducer方法,从而改变状态,达到改变页面UI的这样一个过程。所以我们会先写一个Reducer函数,然后通过useReducer()返回给我们的state和dispatch来驱动这个数据流。

轻量级的redux

const AddCount = () => {
  const reducer = (state, action) =>  {
    if (action.type === 'add') {
      return {
        ...state,
        count: state.count + 1,
      }
    } else {
      return state
    }
  }
  const addcount = () => { 
    dispatch({
      type: 'add'
    })
  }
  const [state, dispatch] = useReducer(reducer, {count: 0})

  return (
    <>
    <p>{state.count}</p>
    <button onClick={addcount}>count++</button>
    </>
  )
}
  1. useEffect 副作用钩子

使用最广泛的hooks

// 实现componentDidMount
const AsyncPage = () => {
  const [loading, setLoading] = useState(true)
  useEffect(() => {
    setTimeout(()=> {
      setLoading(false)
    },5000)

    return () => {
      // 实现componentWillUnmount
    }
  })
  return (
    loading ? <p>Loading...</p>: <p>异步请求完成</p>
  )
}

// 实现依赖项update检测
const AsyncPage = ({name}) => {
  const [loading, setLoading] = useState(true)
  const [person, setPerson] = useState({})

  // 可检测状态、props变化
  useEffect(() => {
    setLoading(true)
    setTimeout(() => {
      setLoading(false)
      setPerson({name})
    }, 2000)
  }, [name])
  return (
    <>
      {loading ? <p>Loading...</p> : <p>{person.name}</p>}
    </>
  )
}
  1. useMemouseCallback

在class的时代,我们一般是通过pureComponent来对数据进行一次浅比较,引入Hook特性后,我们可以使用Memo进行性能提升

在一个state变化后,默认react会重新执行整个render函数,使用memo可以提高性能

const Child1 = React.memo((props) => {
  console.log("执行子组件1了");
  return <div>子组件1上的n:{props.value}</div>;
});

const Child2 = React.memo((props) => {
  console.log("执行子组件2了");
  return <div>子组件2上的m:{props.value}</div>;
});

// 父组件
return (
  <>
    <div>
      最外层盒子
      <Child1 value={n} />
      <Child2 value={m} />
      <button
        onClick={() => {
          setN(n + 1);
        }}
      >
        n+1
      </button>
      <button
        onClick={() => {
          setM(m + 1);
        }}
      >
        m+1
      </button>
    </div>
  </>
);

// 这样在点击了父组件的按钮后,只会触发局部子组件的刷新

但是将修改state的函数交给子组件后就会失灵, 由于复杂数据类型的地址可能发生改变,于是传递给子组件的props也会发生变化. 这时候就需要 useMemo

<Child2 value={m} onClick={addM} /> //addM是修改M的函数

useMemo使用,与useEffect类似:

const addM = useMemo(() => {
  return () => {
    setM({ m: m.m + 1 });
  };
}, [m]); //表示监控m变化

// 语法糖形式
const addM = useCallback(() => {
  setM({ m: m.m + 1 });
}, [m]);

// 最终父组件写作
<>
  <div>
    最外层盒子
    <Child1 value={n} click={addN} />
    <Child2 value={m} click={addM} />
    <button onClick={addN}>n+1</button>
    <button onClick={addM}>m+1</button>
  </div>
</>

上面的例子中,使用了useCallback来和useMemo对比。其实就是不同的语法糖。useCallback接受一个回调函数并缓存,useMemo接受回调函数并且执行以后再缓存。

基于上面的特性,useMemo可以实现vue中的计算属性的功能:

// num1 num2通过props获取
const sum = useMemo(() => num1 + num2, [num1, num2]);

...
// 动态显示两个变量的和
<div>{sum}</div>

10. 扩展阅读

setState

setState是react的异步操作,每次调用setState都会触发更新,异步操作是为了提高性能,将多个状态合并一起更新,减少re-render调用。

先做一个测试

state = {count: 0}

//对象式的setState
//1.获取原来的count值
const {count} = this.state
//2.更新状态
this.setState({count:count+1},() => {
  console.log(this.state.count);
})
console.log('输出',this.state.count); //0

// React会将多个setState的调用合并成一个来执行,这意味着当调用setState时,state并不会立即更新, 可以使用一个回调来拿上一个更新的值
this.setState( preState => ({count:preState.count+1}))

由此初步推断setState实现了如下两步,合并state,和一次性render:

setState( stateChange ) {
  Object.assign( this.state, stateChange );//合并接收到的state
  renderComponent( this );//调用render渲染组件(应为异步)
}

由于渲染dom是异步的,故而推测有一个渲染队列:

//创建一个队列
const queue = [];
/**
* 该方法用于保存传过来的一个个state和其对应要更新的组件,但是并不更新,而是先放入该队列中等待操作
*/
const renderQueue = [];
function enqueueSetState( stateChange, component ) {
    queue.push( {
        stateChange,
        component
    } );
    // 如果renderQueue里没有当前组件,则添加到队列中
    if ( !renderQueue.some( item => item === component ) ) {
        renderQueue.push( component );
    }
}

进而改写setState方法:

setState( stateChange ) {
  enqueueSetState( stateChange, this );
}

既然有了队列,肯定要有出队操作:

function flush() {
  let item;
  // 遍历
  while( item = setStateQueue.shift() ) {

      const { stateChange, component } = item;

      // 如果没有prevState,则将当前的state作为初始的prevState
      if ( !component.prevState ) {
          component.prevState = Object.assign( {}, component.state );
      }

      // 如果stateChange是一个方法,也就是setState的第二种形式
      if ( typeof stateChange === 'function' ) {
          Object.assign( component.state, stateChange( component.prevState, component.props ) );
      } else {
          // 如果stateChange是一个对象,则直接合并到setState中
          Object.assign( component.state, stateChange );
      }

      component.prevState = component.state;
  }
}

如此就实现了使用队列更新state。下面说说dom渲染时机。渲染组件不能在遍历队列时进行,因为同一个组件可能会多次添加到队列中,我们需要另一个队列保存所有组件,不同之处是,这个队列内不会有重复的组件。

// 这也是为什么会有这个声明
const renderQueue = [];

在flush中末尾加入渲染方法即可:

// 渲染每一个组件, 至于renderComponent怎么实现,牵扯到fiber的原理部分了,应该不是入门时该考虑的
while( component = renderQueue.shift() ) {
    renderComponent( component );
}

还有一点没有说明,那就是flush的时机,到底积攒多少state才触发一次更改呢:

// 可以利用js事件队列,让setState在同步任务后执行,enqueueSetState函数顶部加入,在同步的push动作完成后才会执行defer
if ( queue.length === 0 ) {
  defer( flush );
}

function defer( fn ) {
  return Promise.resolve().then( fn );
}

别的延迟方法

function defer( fn ) {
    // 16毫秒一次
    return requestAnimationFrame( fn );
}

最新的React中,内部机制能检测到的地方, setState就是异步的;在React检测不到的地方,例如setInterval, setTimeout里,setState就是同步触发更新的。

路由懒加载应用

<div className="panel-body">
  <Suspense fallback={<Loading/>}>
    {/* 注册路由 */}
    <Route path="/about" component={About}/>
    <Route path="/home" component={Home}/>
  </Suspense>
</div>

Fragment铺平dom层级

<Fragment key={1}>
  <input type="text"/>
  <input type="text"/>
</Fragment>

<Fragment> 可简写为 <>

PureComponent

PureComponent自带通过props和state的浅对比来实现 shouldComponentUpate(),而Component没有。所以继承了PureComponent后不需要开发者自己实现shouldComponentUpdate,就可以进行简单的判断来提升性能。

renderProps

指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术

class Parent extends React.Component {
  render() {
    return (
        <div className="parent">
            <h3>我是Parent组件</h3>
            <A render={(name) => <C name={name}/>}/>
        </div>
    )
  }
}

const C = ({ name }) => <span>{name}</span>

class A extends React.Component {
  constructor(props) {
    super();
    this.state = {name:'tom'}
  }
  render() {
    const {name} = this.state
    return (
        <div className="a">
            <h3>我是A组件</h3>
            {/* 重点 */}
            {this.props.render(name)}
        </div>
    )
  }
}

ReactDOM.createRoot(document.getElementById('test')).render(
    <Parent />
)

渲染结果:

image.png

getDerivedStateFromError接受error

export default class Parent extends Component {
  constructor(props) {
    super();
    this.hasError = '' //用于标识子组件是否产生错误
  }

  //当Parent的子组件出现报错时候,会触发getDerivedStateFromError调用,并携带错误信息
  static getDerivedStateFromError(error){
    return {hasError: error}
  }

  // 当有错误发生时, 可以友好地展示 fallback 组件;可以捕捉到它的子元素(包括嵌套子元素)抛出的异常;可以复用错误组件;
  componentDidCatch(){
    console.log('此处统计render()错误,反馈给服务器,用于通知编码人员进行bug的解决');
  }

    render() {
        return (
            <div>
                <h2>我是Parent组件</h2>
                {this.state.hasError ? <h2>当前网络不稳定,稍后再试</h2> : <Child/>}
            </div>
        )
    }
}

11. 实战

todoList案例