react 基础 - 04

163 阅读5分钟

react 基础

jsx 知识介绍

引入react.js 和 react-dom.js (dom 操作)

jsx 模版与普通xml区别

<div className="foo">bar</div>
<div className="foo">{something ? "something is true" : "something is false" }</div>

jsx 中可以使用一些表达式,不支持语句 表达式一般要加分号 语句需要加分号

<div className="foo">{if (xxx) {xxx} else {xxx} }</div> // 错误

在 jsx 中我们需要保证渲染的内容必须是合法的 jsx 元 素,合法的 jsx 元素有:

  • 普通的 DOM 标签,如 div/p/span 等等
  • 声明的 react 组件,例如通过 class 或函数创建的 jsx 组件
function Select() {return (<p>test</p>)}
class App extends Component {
  render() {
    const ComponentName = Select;
    return(
      <ComponentName className="App">
        hello world
        // 可以通过三目判断变量,如果是true 渲染xx,false 渲染 yy
        { null ? (<p>true</p>) : (<p>false</p>)}
      </ComponentName>  
    )
  }
}
  • null(最终会渲染一个空元素)
  • 字符串(最终会渲染一个 text 节点)
  • 对于数字类型,最终就会渲染出来,所以有的时候通 过布尔表达式判断的时候就会有问题
{false && (<p>this is false</p>)} // 不会渲染内容
{0 && (<p>this is false</p>)} // 会渲染 0

因此不推荐 && || 去写,推荐三目写,哪怕多需要写一个null

我们可以通过React.isValidElement 来判断是不是一个合法的react element

同时需要注意的是,因为 class / for 这类的 html 属性是 关键字,所以在 jsx 中我们想要使用,就必须使用 className/htmlFor 的形式来定义。

 <label className="foo"
htmlFor="name">label</label>

事实上我们并不能直接在浏览器中使用 jsx 内容,我们 需要搭配一些编译库将 jsx 语法进行编译。比较知名的 就是 babel,搭配 babel-plugin-transform-react-jsx 插 件,可以将 jsx 编译为 react 的内部方法 例如这个例子,经过 babel 和配套插件就可以将这种形 式进行编译:

 <div>
  <h3 className="h3">{something ? "something is
true" : "something is false"}</h3>
</div>

最终结果即为:

React.createElement(
  "div", // tagName
  null, // props
  React.createElement(// children
    "h3",
    {className: 'h3'},
    something ? "something is true" :
"something is false"
) );

React.createElement 主要分为三类参数,第一个是组件 的名字,第二个参数是当前组件接受的属性,第三个之 后的参数都是当前组件嵌套的子组件。

jsx 总结

  • jsx 是一种语法糖,我们需要将他们编译为React.createElement 的形式
  • 写 jsx 需要注意类型必须合法,尤其是写布尔表达式 的时候需要额外注意,尽量使用三目运算符来书写 jsx
  • 需要注意 class 和 for 标签在书写时需要改为 classNamehtmlFor

create-react-app cli 的使用

npm install -g create-react-app

函数组件和 class 组件/ 受控组件和非受控组件

在 react 中,我们可以使用 class 形式或是函数的形式来 进行创建一个组件,例如以下两种形式:

function Foo(props) { return (<div>{props.text
|| 'Foo'}</div>); }

class Bar extends React.Component {
  render() {
    return (
      <div>{this.props.text || 'Bar'}</div>
); }
}

二者区别:

  • 加载的 props 方式不同,函数式定义组件从组件函数 的参数加载。class 形式的组件通过 this.props 获取传 入的参数
  • 函数式组件比较简单,内部无法维护状态。class 形式 内部可以通过 this.state 和 this.setState 方法更新内部 state 和更新内部 state,同时更新 render 里面的函数 渲染的结果。
  • class 组件内部可以定义更多的方法在实例上,以及生命周期,但是函数式组件无法定义。

受控组件和非受控组件:

非受控组件: input 值在input 组件里面,通过 e.target.value 可以直接获取

<input value="123">

受控组件: 监听input 事件,保存在 state 里面,通过state 渲染出value

<input value={ val }>

组件生命周期

在 class 形式的组件中,我们可以定义以下声明周期的方 法,会在特定的时机执行它。(老版本)

componentWillMount -> render -> componentDidMount 更新:componentWillReceiveProps -> componentWillUpdate -> render -> componentDidUpdate

render 方法:通过 jsx 拿到DOM

这里需要注意的就是,同 vue 的声明周期一样,我们最好在 componentDidMount 中发送请求,原因:

componentWillMount 是在服务端渲染执行的方法,在里面发送请求的话,不符合服务端渲染数据获取的方式;比如浏览器中 有fetch,而node中没有;请求中引用一些window上的对象,造成组件在服务端渲染挂掉

客户端发送请求的api 一定放在服务端不会执行的生命周期中;同时api中引用客户端中的操作不会影响

常见错误和性能问题

异步过程使用单例的 event 对象

react 中的 event 对象全局单例共享,每一个点击都不会创建新的实例

class App extends Component {
  handleClick1() {
    setTimeout(function(e) {
      // undefined
      console.log('click button 1', e.currentTarget.innerText);
    }, 1000)
  }
  handleClick2(e) {
    // click button 2 Click 2
    console.log('click button 2', e.currentTarget.innerText)
  }
  render() {
    return (
      <div className="App">
        <button onClick={this.handleClick1}>Click 1</button>
        <button onClick={this.handleClick2}>Click 2</button>
      </div>
    )
  }
}

react 事件并不是原生的 e,而是react 代理的构造的 event;异步中获取,到达回调的时候,里面的 event 可能被其他值覆盖了;

-> 因此在异步操作的时候,需要先把 event对象赋值出来

 handleClick1() {
   const text = e.currentTarget.innerText
    setTimeout(function(e) {
      // undefined
      console.log('click button 1', text;
    }, 1000)
  }

代理的原因:

  1. 代理之后内部可以做一些优化,绑定事件过多的话,大型项目会出问题,原生还需要手动销毁;
  2. 跨端兼容性较好

rerender 问题

class RenderText extends Component {
  render() {
    return (
      <div>{this.props.text}</div>
    )
  }
}

class App extends Component {
  state = {
    text: ''
  }
  renderRandomText() {
    this.setState({text: 'text'}) 
  }
  render() {
    return (
      <div className="App">
        <button onClick={this.renderRandomText.bind(this)}>Click 3</button>
        <RenderText text={this.state.text} renderRandomText={this.renderRandomText.bind(this)}/>
    )
  }

页面中会隐藏严重的性能问题: 向子组件传入引用类型的值(对象、数组、函数),需要绑定 this,每次执行render的时候,组件每次重新渲染

解决:

  1. 不要在 render 中写箭头函数
  2. 通常将上一步写在constructor 中
class App extends Component {
  constructor(props) {
    super(props);
    this.renderRandomText = this.renderRandomText.bind(this)
  }
  render() {
    return (
      <div className="App">
        <button onClick={this.renderRandomText.bind(this)}>Click 3</button>
        <RenderText text={this.state.text} renderRandomText={this.renderRandomText}/>
    )
  }
}

render 当中不要绑定函数,尽量都在 constructor 中绑定

引用举例:

const a = [{val: 1}]

const b = a.map(item => item.val = 2)

console.log(a[0], b[0])

深拷贝的问题,完全断绝引用

-> 使用 immutable 库

const immutable = require('immutable');
const data = {
  key1: { key1Key1: 'valuekey1' },
  key2: { key2Key1: 'valuekey2' },
}

const a = immutable.fromJS(data);

// b.key1 = 'valueb'
const b = a.set('key1', 'valueb')

console.log(a.get('key1'), b.get('key1'), a.key2 === b.key2);
// {"key1Key1": "valuekey1"} 'valueb' true

改变了key1的值,但是还保持这key2的引用

immutable 库 immutable-js 和 immer

为了配合 shouldComponentUpdate 来进行性能优化,大 部分时候我们需要复杂的层级判断,这里我们介绍两个 配合 react 最小更新的 immutable 库 immutable-js 和 immer。

immer 库

值变化才断开

const {produce} = require('immer')

const state = {
  key1: 'test'
}

// const newState = (state.key1 = 'newKey1')
const newStet = produce(state, (draft) => {
  draft.key1 = 'newKey1';
})

console.log(newState.key1, state.key1)
// 相对immutable api 较为简单

总结:一切为了性能优化,rerender,对于函数绑定来说,放在constructor里,对于对象来说,使用immutable 和 immuer