5.说说你对受控组件和非受控组件的理解?应用场景?
受控组件:即通过setState的形式控制输入的值及更新,
非受控组件:即通过dom的形式更新值,要获取其值可以通过ref的形式去获取。
受控组件
受控组件,简单来讲,就是受我们控制的组件,组件的状态全程响应外部数据
举个简单的例子:
class TestComponent extends React.Component { constructor (props) { super(props); this.state = { username: 'lindaidai' }; } render () { return <input name="username" value={this.state.username} /> } }
这时候当我们在输入框输入内容的时候,会发现输入的内容并无法显示出来,也就是input标签是一个可读的状态
这是因为value被this.state.username所控制住。当用户输入新的内容时,this.state.username并不会自动更新,这样的话input内的内容也就不会变了
如果想要解除被控制,可以为input标签设置onChange事件,输入的时候触发事件函数,在函数内部实现state的更新,从而导致input框的内容页发现改变
因此,受控组件我们一般需要初始状态和一个状态更新事件函数
非受控组件
非受控组件,简单来讲,就是不受我们控制的组件
一般情况是在初始化的时候接受外部数据,然后自己在内部存储其自身状态
当需要时,可以使用ref 查询 DOM 并查找其当前值,如下:
import React, { Component } from 'react'; export class UnControll extends Component { constructor (props) { super(props); this.inputRef = React.createRef(); } handleSubmit = (e) => { console.log('我们可以获得input内的值为', this.inputRef.current.value); e.preventDefault(); } render () { return ( <form onSubmit={e => this.handleSubmit(e)}> <input defaultValue="lindaidai" ref={this.inputRef} /> <input type="submit" value="提交" /> </form> ) } }
应用场景
大部分时候推荐使用受控组件来实现表单,因为在受控组件中,表单数据由React组件负责处理
如果选择非受控组件的话,控制能力较弱,表单数据就由DOM本身处理,但更加方便快捷,代码量少
针对两者的区别,其应用场景如下图所示:
6.谈谈你对react事件处理的理解?
React事件机制
React基于浏览器事件机制实现了一套自己的事件机制,包括:事件注册、事件合成、事件冒泡、事件触发等。
React中的事件处理是什么?
React 合成事件(SyntheticEvent)是 React 模拟原生 DOM 事件所有能力的一个事件对象,即浏览器原生事件的跨浏览器包装器。它根据 W3C 规范 来定义合成事件,兼容所有浏览器,拥有与浏览器原生事件相同的接口。
为什么要使用合成事件?
进行浏览器兼容,实现更好的跨平台
避免垃圾回收
方便事件统一管理
合成事件与原生事件的区别?
React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
合成事件必须显式的使用 preventDefault来组织默认行为,原生事件中可通过return false来实现
使用合成事件不需要使用 addEventListener 为已创建的 DOM 元素添加监听器。只需要在该元素初始渲染的时候添加监听器即可。
向合成事件传参须通过箭头函数或 Function.prototype.bind 来实现
合成事件与原生事件的执行顺序
React 所有事件都挂载在 document 对象上;当真实 DOM 元素触发事件,会冒泡到 document 对象后,再处理 React 事件;所以会先执行原生事件,然后处理 React 事件;最后真正执行 document 上挂载的事件。
所有的原生事件都有对应的合成事件吗?
不是。「几乎」所有事件都代理到了 document,说明有例外,比如audio、video标签的一些媒体事件(如 onplay、onpause 等),是 document 所不具有,这些事件只能够在这些标签上进行事件进行代理,但依旧用统一的入口分发函数(dispatchEvent)进行绑定。
React事件机制总结
React 上注册的事件最终会绑定在document这个 DOM 上,而不是 React 组件对应的 DOM(减少内存开销就是因为所有的事件都绑定在 document 上,其他节点没有绑定事件)
React 自身实现了一套事件冒泡机制,所以这也就是为什么我们 event.stopPropagation()无效的原因。
React 通过队列的形式,从触发的组件向父组件回溯,然后调用他们 JSX 中定义的 callback
React 有一套自己的合成事件 SyntheticEvent
7.谈谈你对redux的理解?
redux是什么
React是用于构建用户界面的,帮助我们解决渲染DOM的过程
而在整个应用中会存在很多个组件,每个组件的state是由自身进行管理,包括组件定义自身的state、组件之间的通信通过props传递、使用Context实现数据共享
如果让每个组件都存储自身相关的状态,理论上来讲不会影响应用的运行,但在开发及后续维护阶段,我们将花费大量精力去查询状态的变化过程
这种情况下,如果将所有的状态进行集中管理,当需要更新状态的时候,仅需要对这个管理集中处理,而不用去关心状态是如何分发到每一个组件内部的
redux就是一个实现上述集中管理的容器,遵循三大基本原则:
单一数据源
state 是只读的
使用纯函数来执行修改
注意的是,redux并不是只应用在react中,还与其他界面库一起使用,如Vue
redux工作原理
redux 要求我们把数据都放在 store 公共存储空间
一个组件改变了 store 里的数据内容,其他组件就能感知到 store 的变化,再来取数据,从而间接的实现了这些数据传递的功能
工作流程图如下所示:
根据流程图,可以想象,React Components 是借书的用户, Action Creactor 是借书时说的话(借什么书), Store 是图书馆管理员,Reducer 是记录本(借什么书,还什么书,在哪儿,需要查一下), state 是书籍信息
整个流程就是借书的用户需要先存在,然后需要借书,需要一句话来描述借什么书,图书馆管理员听到后需要查一下记录本,了解图书的位置,最后图书馆管理员会把这本书给到这个借书人
转换为代码是,React Components 需要获取一些数据, 然后它就告知 Store 需要获取数据,这就是就是 Action Creactor , Store 接收到之后去 Reducer 查一下, Reducer 会告诉 Store 应该给这个组件什么数据
redux如何使用
创建一个store的公共数据区域
import { createStore } from 'redux' // 引入一个第三方的方法 const store = createStore() // 创建数据的公共存储区域(管理员)
还需要创建一个记录本去辅助管理数据,也就是reduecer,本质就是一个函数,接收两个参数state,action,返回state
// 设置默认值 const initialState = { counter: 0 } const reducer = (state = initialState, action) => { }
然后就可以将记录本传递给store,两者建立连接。如下:
const store = createStore(reducer)
如果想要获取store里面的数据,则通过store.getState()来获取当前state
console.log(store.getState());
下面再看看如何更改store里面数据,是通过dispatch来派发action,通常action中都会有type属性,也可以携带其他的数据
store.dispatch({ type: "INCREMENT" }) store.dispath({ type: "DECREMENT" }) store.dispatch({ type: "ADD_NUMBER", number: 5 })
下面再来看看修改reducer中的处理逻辑:
const reducer = (state = initialState, action) => { switch (action.type) { case "INCREMENT": return {...state, counter: state.counter + 1}; case "DECREMENT": return {...state, counter: state.counter - 1}; case "ADD_NUMBER": return {...state, counter: state.counter + action.number} default: return state; } }
注意,reducer是一个纯函数,不需要直接修改state
这样派发action之后,既可以通过store.subscribe监听store的变化,如下:
store.subscribe(() => { console.log(store.getState()); })
在React项目中,会搭配react-redux进行使用
完整代码如下:
const redux = require('redux'); const initialState = { counter: 0 } // 创建reducer const reducer = (state = initialState, action) => { switch (action.type) { case "INCREMENT": return {...state, counter: state.counter + 1}; case "DECREMENT": return {...state, counter: state.counter - 1}; case "ADD_NUMBER": return {...state, counter: state.counter + action.number} default: return state; } } // 根据reducer创建store const store = redux.createStore(reducer); store.subscribe(() => { console.log(store.getState()); }) // 修改store中的state store.dispatch({ type: "INCREMENT" }) // console.log(store.getState()); store.dispatch({ type: "DECREMENT" }) // console.log(store.getState()); store.dispatch({ type: "ADD_NUMBER", number: 5 }) // console.log(store.getState());
小结
createStore可以帮助创建 store
store.dispatch 帮助派发 action , action 会传递给 store
store.getState 这个方法可以帮助获取 store 里边所有的数据内容
store.subscrible 方法订阅 store 的改变,只要 store 发生改变, store.subscrible 这个函数接收的这个回调函数就会被执行
8.讲讲 Redux 和 Vuex区别?
相同点
state 共享数据
流程一致:定义全局state,触发,修改state
原理相似,通过全局注入store。
不同点
从实现原理上来说:
- Redux 使用的是不可变数据,而Vuex的数据是可变的。Redux每次都是用新的state替换旧的state,而Vuex是直接修改
- Redux 在检测数据变化的时候,是通过 diff 的方式比较差异的,而Vuex其实和Vue的原理一样,是通过 getter/setter来比较的
从表现层来说:
- vuex定义了state、getter、mutation、action四个对象;redux定义了state、reducer、action。
- vuex中state统一存放,方便理解;reduxstate依赖所有reducer的初始值
- vuex有getter,目的是快捷得到state;redux没有这层,react-redux mapStateToProps参数做了这个工作。
- vuex中mutation只是单纯赋值(很浅的一层);redux中reducer只是单纯设置新state(很浅的一层)。他俩作用类似,但书写方式不同
- vuex中action有较为复杂的异步ajax请求;redux中action中可简单可复杂,简单就直接发送数据对象({type:xxx, your-data}),复杂需要调用异步ajax(依赖redux-thunk插件)。
- vuex触发方式有两种commit同步和dispatch异步;redux同步和异步都使用dispatch
通俗点理解就是,vuex 弱化 dispatch,通过commit进行 store状态的一次更变;取消了action概念,不必传入特定的 action形式进行指定变更;弱化reducer,基于commit参数直接对数据进行转变,使得框架更加简易;
共同思想
单一的数据源
变化可以预测
本质上∶ redux与vuex都是对mvvm思想的服务,将数据从视图中抽离的一种方案。
9.说说Connect组件用法及原理?
1. connect用法
作用:连接React组件与 Redux store
connect([mapStateToProps], [mapDispatchToProps], [mergeProps],[options]) // 这个函数允许我们将 store 中的数据作为 props 绑定到组件上 const mapStateToProps = (state) => { return { count: state.count } }
这个函数的第一个参数就是 Redux 的 store,我们从中摘取了 count 属性。你不必将 state 中的数据原封不动地传入组件,可以根据 state 中的数据,动态地输出组件需要的(最小)属性
函数的第二个参数 ownProps,是组件自己的 props
当 state 变化,或者 ownProps 变化的时候,mapStateToProps 都会被调用,计算出一个新的 stateProps,(在与 ownProps merge 后)更新给组件
mapDispatchToProps(dispatch, ownProps): dispatchProps
connect 的第二个参数是 mapDispatchToProps,它的功能是,将 action 作为 props绑定到组件上,也会成为 MyComp 的 props
2. 原理解析
首先connect之所以会成功,是因为Provider组件
在原应用组件上包裹一层,使原来整个应用成为Provider的子组件
接收Redux的store作为props,通过context对象传递给子孙组件上的connect
connect做了些什么
它真正连接 Redux 和 React,它包在我们的容器组件的外一层,它接收上面 Provider提供的 store 里面的 state和 dispatch,传给一个构造函数,返回一个对象,以属性形式传给我们的容器组件
3. 源码
connect是一个高阶函数,首先传入mapStateToProps、mapDispatchToProps,然后返回一个生产Component的函数(wrapWithConnect),然后再将真正的Component作为参数传入wrapWithConnect,这样就生产出一个经过包裹的Connect组件,该组件具有如下特点
通过props.store获取祖先Component的store props包括stateProps、dispatchProps、parentProps,合并在一起得到nextState,作为props传给真正的Component
componentDidMount时,添加事件this.store.subscribe(this.handleChange),实现页面交互
shouldComponentUpdate时判断是否有避免进行渲染,提升页面性能,并得到nextState
componentWillUnmount时移除注册的事件this.handleChange
// 主要逻辑 export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) { return function wrapWithConnect(WrappedComponent) { class Connect extends Component { constructor(props, context) { // 从祖先Component处获得store this.store = props.store || context.store this.stateProps = computeStateProps(this.store, props) this.dispatchProps = computeDispatchProps(this.store, props) this.state = { storeState: null } // 对stateProps、dispatchProps、parentProps进行合并 this.updateState() } shouldComponentUpdate(nextProps, nextState) { // 进行判断,当数据发生改变时,Component重新渲染 if (propsChanged || mapStateProducedChange || dispatchPropsChanged) { this.updateState(nextProps) return true } } componentDidMount() { // 改变Component的state this.store.subscribe(() = { this.setState({ storeState: this.store.getState() }) }) } render() { // 生成包裹组件Connect return ( <WrappedComponent {...this.nextState} /> ) } } Connect.contextTypes = { store: storeShape } return Connect; } }

