讓react用起來更得心應手——(React 基礎簡析)

夢想攻城獅發表於2018-10-22

讓react用起來更得心應手系列文章:

  1. 讓react用起來更得心應手——(react基礎簡析)
  2. 讓react用起來更得心應手——(react-router原理簡析)
  3. 讓react用起來更得心應手——(react-redux原理簡析)

react API

import React from 'react'
import ReactDom from 'react-dom'
let el=<h1><span>hello</span><span>wrold</span></h1>
console.log(el)
ReactDom.render(el,window.root);
複製程式碼
  1. 利用babel中的babel-preset-react進行轉譯,呼叫React.createElement(type,props,children)生成一個虛擬dom節點:
    babel-el
  2. React.createElement原理簡析:
function createElement(type,props,children){
    let obj={};
    obj.type=type;
    obj.props={};
    obj.props.children=null;
    for(let key in props){
        obj.props[key]=props[key]
    }
    children.forEach(child => {
        let {type,props,subchildren} = child
        obj.props.children.push(createElement(type,props,subchildren))
    })
    return obj
}
複製程式碼

el
3. ReactDom.render(vnode,root)會將其渲染出到頁面root中:
index.html
4. ReactDom.render原理簡析:

function render(vnode,container){
    if(typeof vnode ==='string') return container.appendChild(document.createTextNode(vnode));
    let{type,props} = vnode;
    let tag = document.createElement(type);
    for(let key in props){
        if(key==='children'){
            Array.from(props[key]).forEach(child => {
                render(child,tag)
            });
        }else{
            tag.setAttribute(key,props[key]);
        }
        
    }
    container.appendChild(tag);
}
複製程式碼

react元件

函式宣告元件

  1. 函式式宣告元件:函式返回一個JSX語法的vnode
import React from 'react'
import ReactDom from 'react-dom'
function test(){
    let state = {
        title:'標題',
        context:'內容'
    }
    return (
    <div>
        <h3>{state.title}</h3>
        <p>{state.context}</p>
    </div>
    )
}
ReactDom.render(
<div>
{test()}
{test()}
{test()}    
</div>,window.root);
複製程式碼
  1. 將上面的函式宣告元件進行一定的包裝
import React from 'react'
import ReactDom from 'react-dom'
function Test(props){
    let state={
        title:"標題"
    }
    return (
    <div>
        <h3>{state.title}</h3>
        <p>{props.context}</p>
    </div>
    )
}
ReactDom.render(
<div>
<Test context='123'></Test>
<Test context='456'></Test>
<Test context='789'></Test>      
</div>,window.root);
複製程式碼

函式宣告元件存在一些問題:

  1. 沒有this
  2. 沒有狀態,state無法進行動態更改
  3. 沒有生命週期

類元件

類元件消除了函式宣告元件的問題,也是現在寫react的正常語法。

import React, { Component } from 'react';
import ReactDOM, { render } from 'react-dom';
import PropTypes from 'prop-types'; //屬性校驗外掛

class Clock extends Component {
  state = {
    time: new Date().toLocaleString()
  }
  static propTypes = { 
    name: PropTypes.string.isRequired   //name必填,校驗是否填寫name屬性
  }
   // 元件掛載後呼叫
  componentDidMount() {
    this.timer = setInterval(() => {
      // 只會覆蓋以前的屬性類似 Object.assign()
      this.setState({ time: new Date().toLocaleString() })    
    }, 1000)
  }
  
  handleClick = () => { //箭頭函式,this用最外層的this,指向當前元件
    ReactDOM.unmountComponentAtNode(window.root)
  }
  
  // 元件解除安裝前呼叫,一般用於解綁事件和方法
  componentWillUnmount() { 
    clearInterval(this.timer)
  }
  
  // 預設渲染這個元件會呼叫render方法
  // 只是在上面函式式宣告基礎上包了一層函式,這樣可以控制其執行時間,新增宣告週期
  render() { 
    let {name} = this.props     //一般會解構props
    return <div>
      {name} <span>{this.state.time}</span>
      <button onClick={this.handleClick}>刪除</button>
    </div>
  }
}
render(<Clock name='test-clock'/>, window.root);
複製程式碼

react 生命週期

// React16.3 推出了新的宣告週期 
import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class Counter extends Component {
  static defaultProps = { 
    a: 1
  }
  state = {
    num: 0
  }
  constructor(props) {
    console.log('parent-constructor')
    super();
  }

  // react16.3中標識了這個方法會被廢棄掉
  // 後期有需要的話 可以放在constructor中替代掉 
  componentWillMount() { 
    console.log('parent-componentWillMount');
  }

  // react的效能優化 immutablejs
  shouldComponentUpdate(){ 
    console.log('parent-shouldComponentUpdate');
    return true
  }
  componentWillReceiveProps(){ 
    console.log('parent-componentWillReceiveProps');
  }
  componentWillUpdate(){
    console.log('parent-componentWillUpdate');
  }
  componentDidUpdate() {
    console.log('parent-componentDidUpdate');
  }
  handleClick = () => {
    this.setState({ num: this.state.num + 0 });
  }
  render() {
    console.log('parent-render');
    return <div>
      <button onClick={this.handleClick}>+</button> 
      {this.state.num}
      <ChildCounter n={this.state.num}></ChildCounter>
      </div>
  }
  componentDidMount() {
    console.log('parent-didmount');
  }
  componentWillUnmount() {
    console.log('parent-元件解除安裝')
  }
}
class ChildCounter extends Component{
  componentWillMount(){
    console.log('child-componentWillMount')
  }
  render(){
    console.log('child-render');
    return <div>child counter {this.props.n}</div>
  }
  componentDidMount() {
    console.log('child-componentDidMount')
  }
  shouldComponentUpdate(){ 
    console.log('child-shouldComponentUpdate');
    return true
  }
  componentWillUpdate(){
    console.log('child-componentWillUpdate');
  }
  componentDidUpdate() {
    console.log('child-componentDidUpdate');
  }
  
  //16.3中這個方法廢棄了
  componentWillReceiveProps(){ 
    console.log('child-componentWillReceiveProps');
  }
}
ReactDOM.render(<Counter></Counter>, window.root);
複製程式碼
  • 初次渲染時列印的結果為:
    render
  • 元件狀態更新時列印的結果為:
    update
    從上面可以得出以下結論:
  1. 生命週期分為初始化生命週期和元件執行生命週期,初始化生命週期為:constructor->componentWillMount->render->componentDidMount
  2. 元件執行生命週期為:(存在props時:componentWillReceiveProps)->shouldComponentUpdate->componentWillUpdate->render->componentDidUpdate
  3. 子元件的生命週期嵌在父元件render後,DidMount和DidUpdate前
  4. shouldComponentUpdate返回false,元件將不會更新
  5. 只有componentWillReceiveProps、componentWillMount、componentDidMount中可以呼叫setState,不應該在componentWillReceiveProps中呼叫setState(但是大家還是這麼用)

受控元件和非受控元件

  • 非受控元件:ref操作dom,很方便;可以和一些地三方庫結合使用
import React,{Component} from 'react';
import {render} from 'react-dom';

class UnControl extends Component{
  b = React.createRef();  // 16.3的api React.createRef()
  handleClick = () =>{
    alert(this.a.value);    // 寫法1
    alert(this.b.current.value)     // 寫法2
  }
  render(){
    return (<div>
      <input type="text" id="username" ref={dom=>this.a=dom}/>
      <input type="text" id="password" ref={this.b}/>
      <button onClick={this.handleClick}>點選</button>
    </div>)
  }
}
render(<UnControl></UnControl>,window.root);
複製程式碼
  • 受控元件:利用e.target和setState來修改狀態並控制dom
import React from 'react';
import ReactDOM from 'react-dom';

class Control extends React.Component{
  state = {
    a:'hello',
    b:'world'
  }
  changeHandler = (e)=>{
    let val = e.target.name
    this.setState({
      [val]:e.target.value
    })
  }
  render(){
    return (
      <div>
        {this.state.a}
        {this.state.b}
        <input type="text" name="a" value={this.state.a} onChange={this.changeHandler}/>
        <input type="text" name="b" value={this.state.b} onChange={this.changeHandler}/>
      </div>
    )
  }
} 
ReactDOM.render(<Control></Control>,window.root);

複製程式碼

react資料傳遞

  • 逐級傳遞:當元件巢狀的層級不超過3層時,使用這種方式傳遞資料簡單,但是巢狀過深時,會很難管理的問題
//index.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import Second1 from './Second1';
import Second from './Second';
class App extends Component {
  state = {
    count:0
  }
  handleCountChange = (num)=>{
    this.setState({count:num})
  }
  render(){
    return(
      <div>
      <Second count={this.state.count}></Second>
      <Second1 count={this.state.count} countChange={this.handleCountChange}></Second1>
      </div>
    )
  }
}
ReactDOM.render(<App></App>, window.root);
複製程式碼
//second.js
import React, { Component } from 'react'
export default class Second extends Component {
  render() {
    let {count} = this.props
    return (
      <div>{count}</div>
    )
  }
}
複製程式碼
//second1.js
import React, { Component } from 'react'
import Third from './Third';
export default class Second1 extends Component {
  render() {
    let {count,countChange} = this.props
    return (
      <Third num={count} numChange={countChange}></Third>
    )
  }
}
複製程式碼
//third.js
import React, { Component } from 'react';

export default  class Third extends Component {
  handleClick =()=>{
    console.log(this.props.num+1);  
    this.props.numChange(this.props.num+1);
  }
  render(){
    return (
        <span onClick={this.handleClick}>+</span>
    )
  }
}
複製程式碼
  • 採用context
import React, { Component } from 'react';
import {Provider} from './context';
import ReactDOM from 'react-dom';
import Second1 from './Second1';
import Second from './Second';
class App extends Component {
  state = {
    count:0
  }
  handleCountChange = (num)=>{
    this.setState({count:num})
  }
  render(){
    return(
      // 提供一個contex,上面掛載了全域性狀態
      <Provider value={{numChange:this.handleCountChange,num:this.state.count}}>
      <Second></Second>
      <Second1></Second1>
      </Provider>
    )
  }
}
ReactDOM.render(<App></App>, window.root);
複製程式碼
// context.js
import React from 'react';
let { Provider, Consumer } = React.createContext(); //react提供的API
export {Provider,Consumer}
複製程式碼
// second.js
import React, { Component } from 'react';
import {Consumer} from './context';
export default class Second extends Component {
  render() {
    return (
      <Consumer>
        {(value)=>{
          return <div>{value.num}</div>
        }}
      </Consumer>
    )
  }
}
複製程式碼
// second1.js
import React, { Component } from 'react'
import Third from './Third';
export default class Second1 extends Component {
  render() {
    return (
      <Third></Third>
    )
  }
}
複製程式碼
//third.js
import React, { Component } from 'react';
import {Consumer} from './context';
export default  class Third extends Component {
  render(){
    return (<Consumer>
      {(value)=>{   //固定語法value就是Provider中的value
        return <span onClick={()=>{value.numChange(value.num + 1)}}>+</span>
      }}
      </Consumer>)
  }
}
複製程式碼

結語:

個人使用一種框架時總有一種想知道為啥這樣用的強迫症,不然用框架用的不舒服,不要求從原始碼上知道其原理,但是必須得從心理上說服自己。

相關文章