React 中的难点解析

前端这点事 58 0

JSX 和虚拟 DOM 的关系

JSX 是一种语法,是一套独立的标准,由 React 引入,但不是 React 独有的,写法上类似 html ,但是有

  • JS 变量和表达式
  • if...else
  • 循环
  • style 和 className
  • 事件

无法直接在浏览器运行,在 React 中需要通过函数( React.createElement )解析成虚拟 DOM...

虚拟 DOM 是 JS 对象, 这个对象有 JSX 的层次结构,用来模拟真实的 DOM ,这个对象拥有一些重要属性,并且更新 UI 主要就是通过对比(DIFF)旧的虚拟 DOM 树 和新的虚拟 DOM 树的区别完成的。

更为详细的介绍参考 手写一个 React

虚拟 DOM的历史由来,看这里

如何理解组件化

页面比较复杂时,拆分成一个个组件再去实现,就比较简单了

组件的封装,由三部分构成

  • 视图
  • 数据
  • 逻辑(数据驱动视图变化)
class Todo extends Component {
  constructor(props) {
    this.state = {
      list: []
    }
  }
  render() {
    return (
      <div>
        <Input addTitle = { this.addTitle.bind(this) }/>
        <List data = { this.state.list }/>
      </div>
    )
  }
  addTitle(title) {
    const currentList = this.state.list
    this.setState({
      list: currentList.concat(title)
    })
  }
}

组件的复用: 可以传递不同的数据

import Input from './input'
import List from './list'

class Todo extends Component {
  constructor(props) {
    super(props)
    this.state = {
      list: []
    }
  }
  render() {
    return (
      <div> {/* 组件的复用 */} 
        <Input addTitle = { this.addTitle.bind(this) }/>
        <List data = { this.state.list }/>
        <List data = { this.state.list }/>
        <List data = { this.state.list }/>
      </div>
    )
  }
}
//子组件
class List extends Component {
  render() {
    const list = this.props.data {/* 通过props拿到父组件的数据 */} 
    return (
      <ul>
        {
          list.map((item, index) => {
            return <li key = {index}>{item}</li>
          })
        }
      </ul>
    )
  }
}

什么是受控组件

 <FInput value={x} onChange={fn}/> 受控组件
 <FInput defaultValue={x} ref={input}/> 非受控组件

受控组件的状态由开发者维护,非受控组件的状态由组件自身维护(不受开发者控制)

什么是高阶组件

高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。

举个

React-Redux 里connect 就是一个高阶组件,比如connect(mapState)(MyComponent)接受组件 MyComponent,返回一个具有状态的新 MyComponent 组件。

组件间通信

父给子传递:通过 props 传递(数据或方法),用 this.props.xxx 来接收

//父组件
<div>
 <TodoItem content={item}/>
</div>

//子组件 TodoItem
<div>
 {this.props.content}
</div>

子给父传递:子组件绑定事件,调用父组件中的方法,去修改父组件中的数据

//子组件
<div onclick={this.handleClick.bind(this)}>
  {this.props.content}
</div>

handleClick() {
  //调用父组件中的 yyy() 
  this.props.xxx()
}

//父组件
<div>
 <TodoItem xxx={this.yyy.bind(this)}/> //把方法传递给子组件
</div>

yyy() {
 //修改数据
}

爷孙传递:可以传两次 props

React 只是视图层框架,若是多层级组件传递,通过传递多次 props ,会变得难以维护,所以出现了 Redux (数据层框架)

Redux 是什么,如何用

把组件中的数据放到 Store 中,通过组件修改了 Store 中的数据,其他组件也会感知到数据的变化,从而做出更新,Redux 是状态容器,提供可预测化的状态管理。

工作流程

引用图书馆场景

React Component:借书的用户
Action Creators: 借什么书(这句话)
Store:图书馆管理员
Reducers: 记录本(书籍的记录)
小A(React Component)对图书馆管理员(Store)说:借一本 '三体'(Action Creators),管理员通过记录本(Reducers)去找这本书,找到之后把书给到小A
  • 获取数据:一个组件去获取 Store 中的数据,对 Store 说:我要获取XX数据(Action Creators创建这句话),Store 接收到后,通过 Reducers 去找到这条数据,然后把数据给到这个组件
  • 改变数据:同理 Store 通过 Reducers 知道如何改变数据,然后 Stroe 修改好数据后,告诉组件数据已修改,可以重新获取数据了

代码演示

Reducer 的创建(管理数据)

//store/reducer.js
const defaultState = {
  inputValue: '',
  list: []
}
export default (state = defaultState, action) => { //state 默认数据/上一次的数据
  //拿之前的数据及 action 做处理,返回新的数据给 store
  if (action.type === 'change_input_value') {
    const newState = JSON.parse(JSON.stringify(state)) //之前的数据深拷贝
    newState.inputValue = action.value  //改变为传递过来的新值
    return newState //传递给 store, 新旧数据做替换
  }
  return state
}

Store 的创建(存贮数据)

//store/index.js
import { createStore } from 'redux'
import reducer from './reducer'

const store = createStore(reducer)
export default store

组件获取数据

import store from './store/index.js'

constructor(props) {
  super(props)
  this.state = store.getState() //获取 store 中的数据
}
<div>
  {this.state.list}
</div>

修改数据:Action 的创建与派发

const action = {
  type: 'change_input_value',
  value: e.target.value
}
store.dispatch(action) //把 action 传递给 store

Store 再把当前数据及 action 转发给 Reducers 去处理,处理完后,store 中的数据会更新

组件中用到此数据的会自动更新

constructor(props) {
  super(props)
  store.subscribe(this.xxx.bind(this)) //只要 store 中的数据发生改变,subscribe 中的 xxx 会自动执行
}

connect 的原理是什么?

react-redux 库提供的一个 API,connect 的作用是让你把组件和store连接起来,产生一个新的组件(connect 是高阶组件)

React 有哪些生命周期函数?分别有什么用?

其他钩子可以不存在,但是 render 函数必须存在,否则会报错,因为组件是继承自 Component 组件,Component 组件默认内置了所有的声明周期钩子,但没有内置 render

在 componentDidMount 这个钩子里请求数据

放在 render 里不合适,render会被反复执行(数据会被频繁改变),此时会重复发送数据请求。

componentDidMount 在组件被挂载到页面上时会执行一次,之后不会再重复执行,所以把数据请求放到这里是比较合适的

componentWillMount 也会执行一次,写网页时,数据请求放到这里也是可以的,但是当写 React Native ,或服务端同构时,可能会产生技术冲突

shouldComponentUpdate 有什么用?(使用场景)

父组件渲染时(数据发生改变),因为父组件中的 render 函数包含子组件内容,所以子组件也跟着重复渲染,但是子组件中的 state, props 没有发生变化,会带来性能上的损耗,如何优化呢

子组件中使用 shouldComponentUpdate 钩子

class TodoItem extends Component {
  //...
  shouldComponentUpdate(nextProps, nextState) {
    //接下来的 content 不等于当前 props 中的 content 说明接收的 content 值发生了变化,需要让组件重新渲染
    if(nextProps.content !== this.props.content) {
      return true
    }else {
      return false
    }
  }
  render() {
    const { content } = this.props
    return (
      <div>
        {content}
      </div>
    )
  }
}

用于在没有必要更新 UI 的时候返回 false,以提高渲染性能

其他提高性能的方法

  1. 改变 this,放到 constructor 里,这个操作只会执行一次
constructor(props) {
  super(props)
  this.xxx = this.xxx.bind(this)
}

2. setState 异步执行,多次数据的改变变成一次来做,降低虚拟 dom 比对的频率

 

标签: React

发表评论 (已有0条评论)

还木有评论哦,快来抢沙发吧~