React.js 中的 、
、
等元素的
value
值如果是受到 React.js 的控制,那么就是受控组件。
React.js 将组件渲染,并且构造 DOM 元素然后塞入页面的过程称为组件的挂载
componentWillMount
:组件挂载开始之前,也就是在组件调用 render
方法之前调用。一些组件启动的动作,包括像 Ajax 数据的拉取操作、一些定时器的启动等,就可以放在 componentWillMount
里面进行componentDidMount
:组件挂载完成以后,也就是 DOM 元素已经插入页面后调用。componentWillUnmount
:组件对应的 DOM 元素从页面中删除之前调用。组件销毁前需要一些数据的清理,例如定时器的清理,就会放在 componentWillUnmount
里面去做。更新阶段的组件生命周期函数:
shouldComponentUpdate(nextProps, nextState)
:你可以通过这个方法控制组件是否重新渲染。如果返回 false
组件就不会重新渲染。这个生命周期在 React.js 性能优化上非常有用。componentWillReceiveProps(nextProps)
:组件从父组件接收到新的 props
之前调用。componentWillUpdate()
:组件开始重新渲染之前调用。componentDidUpdate()
:组件重新渲染并且把更改变更到真实的 DOM 以后调用。我们可以给任意代表 HTML 元素标签加上 ref
从而获取到它 DOM 元素然后调用 DOM API。推荐使用将ref属性设置为回调函数而不是ref使用字符串方式:React之ref详细用法
this.clock=clock}>
//使用时
{this.clock.clientWidth}
class Card
{this.props.children}//这是个数组,此时长度为2,分别为下面的div和p
class App
1
2
dangerouslySetHTML 属性传入对象,这个对象的 __html
属性值就相当于元素的 innerHTML
,这样我们就可以动态渲染元素的 innerHTML
结构了。
constructor() {
super()
this.state = {
content: 'React.js 小书
'
}
}
...
render () {
return (
)
}
//需要引入
import PropTypes from 'prop-types'
static propTypes = {
comment: PropTypes.object.isRequired
}
//isRequired 关键字来强制组件某个参数必须传入:
//不需要引入什么
static defaultProps = {//没传参数时候的默认参数
comments: []
}
defaultProps
、propTypes
。constructor
。_
开头的私有方法。handle*
。render*
开头的方法,有时候 render()
方法里面的内容会分开到不同函数里面进行,这些函数都以 render*
开头。render()
方法。高阶组件的作用其实不言而喻,其实就是为了组件之间的代码复用。组件可能有着某些相同的逻辑,把这些逻辑抽离出来,放到高阶组件中进行复用。高阶组件内部的包装组件和被包装组件之间通过 props
传递数据。
高阶组件就是一个函数,传给它一个组件,它返回一个新的组件。新的组件使用传入的组件作为子组件。
当某个状态被多个组件依赖或者影响的时候,就把该状态提升到这些组件的最近公共父组件中去管理,用 props
传递数据或者函数来管理这种依赖或着影响的行为。
React.js 的 context 其实像就是组件树上某颗子树的全局变量。
React.js 的 context 就是某个组件只要往自己的 context 里面放了某些状态,这个组件之下的所有子组件都直接访问这个状态而不需要通过中间组件的传递。
使用:
getChildContext
这个方法就是设置 context
的过程,它返回的对象就是 context(也就是上图中处于中间的方块),所有的子组件都可以访问到这个对象。一个组件可以通过 getChildContext
方法返回一个对象,这个对象就是子树的 context,提供 context 的组件必须提供 childContextTypes
作为 context 的声明和验证。
如果一个组件设置了 context,那么它的子组件都可以直接访问到里面的内容,它就像这个组件为根的子树的全局变量。任意深度的子组件都可以通过 contextTypes
来声明你想要的 context 里面的哪些状态,然后可以通过 this.context
访问到那些状态。
context 打破了组件和组件之间通过 props
传递数据的规范,极大地增强了组件之间的耦合性。而且,就如全局变量一样,context 里面的数据能被随意接触就能被随意修改,每个组件都能够改 context 里面的内容会导致程序的运行不可预料。
但是这种机制对于前端应用状态管理来说是很有帮助的,因为毕竟很多状态都会在组件之间进行共享,context 会给我们带来很大的方便。一些第三方的前端应用状态管理的库(例如 Redux)就是充分地利用了这种机制给我们提供便利的状态管理服务。但我们一般不需要手动写 context,也不要用它,只需要用好这些第三方的应用状态管理库就行了。
//父组件
static childContextTypes = {
themeColor: PropTypes.string //这是一个大家都用的状态
}
constructor () {
super()
this.state = { themeColor: 'red' }
}
getChildContext () {
return { themeColor: this.state.themeColor }
}
//子组件:
//子组件要获取 context 里面的内容的话,就必须写 contextTypes 来声明和验证你需要获取的状态的类型,它也是必写的,如果你不写就无法获取 context 里面的状态。Title 想获取 themeColor,它是一个字符串,我们就在 contextTypes 里面进行声明。
static contextTypes = {
themeColor: PropTypes.string
}
render () {
return (
this is a test
)
}
}
dispatch
函数。它接受一个参数 action
,这个 action
是一个普通的 JavaScript 对象,里面必须包含一个 type
字段来声明你到底想干什么。dispatch
在 swtich
里面会识别这个 type
字段,能够识别出来的操作才会执行对 appState
的修改。 不得改写参数 不能调用系统 I/O 的API 不能调用 |
createStore
接受一个叫 reducer 的函数作为参数,这个函数规定是一个纯函数,它接受两个参数,一个是 state
,一个是 action,而正是因为它是一个纯函数所以输入同样的state能渲染一样的视图,这就意味着我们不能在reducer中修改state,因此我们只能做复制做替换,通过Object.assign/.../immutable.js的方式
redux模式 1.reducer:初始化state和返回一个新对象(reducer是一个纯函数,并不会修改state而是利用es6扩展运算符结合覆盖产生一个新对象) 2.createStore(reducer):store.dispatch会自动触发reducer执行,要实现这个功能就是必须把reducer传入到createStore中,返回相应的getState,subscribe,dispatch(相应的功能已经在代码注释中阐明) 3.关于中间件:middleware是一个可插拔的机制,而中间件的执行原理其实就是对dispatch方法的扩展,比如redux-logger就是在dispatch前后获取旧的和新的state。 关于applyMiddleware源码下面会展示: |
// 以下代码都是在这边完全手打的,有出错的地方以后我看到会校对,thx
// 首先需要明确middleware,以redux-logger为例,本质就是操作dispatch
// 和源码不完全一致,但思路一样
function reduxLogger(store){
// 为什么不直接用store.dispatch越过这一步呢,因为我们在这个地方需要一个新的dispatch,且看applyMiddlewares
return function (dispatch){
return function(action){
console.info(`before update ${store.getState()}`);
dispatch(action);
console.info(`before update ${store.getState()}`);
}
}
}
// 关于洋葱模型的compose函数
// compose(f1,f2,f3)(args)===>f3(f2(f1(args))),相对源码我稍微做了一点改动,源码是f1(f2(f3(args)))
// https://github.com/reduxjs/redux/blob/v3.7.2/src/compose.js
function compose(...funcs){
if(funcs.length===0){
return args=>args;
}
if(funcs.length===1){
return funcs[0];
}
return funcs.reduce((res,item)=>args=>item(res(args)));
}
//applyMiddlewares,本质还是对dispatch的扩展
function applyMiddlewares(...middlewares){
return (createStore)=>{
return (reducer)=>{
let store=createStore(reducer);
// 一个函数数组,可以调用dispatch返回新的dispatch
let chains=middlewares.map(item=>item(store));
let newDispatch=compose(...chains)(store.dispatch);
return {
...store,
dispatch:newDispatch
}
}
}
}
每一个部分的作用:(原生react-redux原理:react中使用 redux) react中管理数据的流程是单向的,从派发action到发布订阅渲染视图是一条路走到头。 1.reducer:初始化state和返回一个新对象(reducer是一个纯函数,并不会修改state而是利用es6扩展运算符结合覆盖产生一个新对象) 2.createStore(reducer):store.dispatch会自动触发reducer执行,要实现这个功能就是必须把reducer传入到createStore中 3.store放到context里面,让所有子组件都能访问store(结合6) 4.针对大量重复逻辑和对context依赖性过强,使用connect函数返回高阶组件( 5.此函数的实现使用函数式编程:接收两个参数:mapStateToProps(告诉connect取哪部分数据)mapDispatchToProps(告诉connect如何触发dispatch) 6.抽离出来的provider:提供store |
// Provider组件源码
class Provider extends Component {
// 用来设置context对象,返回的就是子树的context
getChildContext() {
return {
store: this.props.store
};
}
render() {
return this.props.children;
}
}
// 用来校验getChildContext返回的context数据类型
Provider.childContextTypes = {
store: React.PropTypes.object
}
// Provider的使用
class xx extends React.Component{
let store=createStore(reducer);
render(){
return{
}
}
}
UI组件特点:
|
容器组件特点:
|
react-redux使用规范:
1.定义 action types 2. 编写 reducer 3. 跟这个 reducer 相关的 action creators(action creators 其实就是返回 action 的函数,这样我们 dispatch({ type: 'INIT_COMMENTS', xxx}) dispatch(initComments(xxx)) 4.划分好 smart组件和dumb组件 5.在index.js里面引入 |
import { Provider } from 'react-redux'
import { createStore } from 'redux'
const store = createStore(commentsReducer)
6.在需要传参进行一些操作的子组件(一般为修改state,然后dispatch)中引入:
import { connect } from 'react-redux'
导出的时候是connect返回的带参数的组件(参数和mapDispatchToProps一致和propTypes一致)
ReactDOM.render(
,
document.getElementById('root')
);
下面讲一下connect 源码:references:React-Redux 源码分析(三)-- connect
/** * 1、从context里获取store * 2、在componentWillMount 里通过mapStateToProps获取stateProp的值 * 3、在componentWillMount 里通过mapDispatchToProps获取dispatchProps的值 * 4、在componentWillMount 里订阅store的变化 * 5、将获得的stateProp,dispatchProps,还有自身的props合成一个props传给下面的组件 * 6,当然其中包含shouldComponentUpdate的性能优化,selectorFactory函数等等 * @param mapStateToProps * @param mapDispatchToProps * @returns {{new(): wrapConnectComponent, prototype: wrapConnectComponent}} * @constructor */ const Connect=(mapStateToProps,mapDispatchToProps)=>{ return (wrappedComponent)=>class wrapConnectComponent extends React.Component{ static contextTypes={ store:PropTypes.object, }; constructor(props){ super(props); this.state={ allProps:{}, } }; componentWillMount(){ const {store}=this.context; this._updateProps(); store.subscribe(()=>this._updateProps()); }; _updateProps(){ // 收集所有的props,setState const {store}=this.context; const stateProps=mapStateToProps?mapStateToProps(store.getState,this.props):{}; const dispatchProps=mapDispatchToProps?mapDispatchToProps(store.getState,this.props):{}; this.setState({ allProps:{ ...this.props, ...stateProps, ...dispatchProps, } }) }; render(){ const {allProps}=this.state; return(
) } } }; References:
react小书
+new Date()是什么意思?:返回从1970年1月1日0时0分0秒,到该日期的毫秒数(时间戳)
知识点总结:
只要组件的状态( props
或者 state
)发生了改变,那么组件就会执行render
函数进行重新渲染。除非你重写 shouldComponentUpdate
周期函数通过返回 false
来阻止这件事的发生;又或者直接让组件直接继承 PureComponent
。
而继承 PureComponent
的原理也很简单,它只不过代替你实现了 shouldComponentUpdate
函数:在函数内对现在和过去的 props
/ state
进行“浅对比”(shallow comparision,具体内容请移步我的:react学习笔记系列(一)即仅仅是比较对象的引用而不是比较对象每个属性的值),如果发现对象前后没有改变则不执行 render
函数对组件进行重新渲染
其实这样一套相似逻辑在 Redux 中也多次存在,在 redux 中也会对数据进行“浅对比”
import {conenct} from 'react-redux'
function mapStateToProps(state) {
return {
todos: state.todos,
visibleTodos: getVisibleTodos(state),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
//--------------------------------------
function mapStateToProps(state) {
return {
data: {
todos: state.todos,
visibleTodos: getVisibleTodos(state),
}
}
}
注意上面两片代码的区别:
代码片1中:
代码中组件 App
是被 react-redux
封装的组件, react-redux
会假设 App
是一个 Pure Component
,即对于唯一的 props
和 state
有唯一的渲染结果。 所以 react-redux
首先会对根状态(即上述代码中 mapStateToProps
的第一个形参 state
)创建索引,进行浅对比,如果对比结果一致则不对组件进行重新渲染,否则继续调用mapStateToProps
函数;同时继续对 mapStateToProps
返回的 props
对象里的每一个属性的值(即上述代码中的 state.todos
值和 getVisibleTodos(state)
值,而不是返回的 props
整个对象)创建索引。和shouldComponentUpdate
类似,只有当浅对比失败,即索引发生更改时才会重新对封装的组件进行渲染:这就解决了之前一个问题:store中数据改变,但是比如:使用todos的组件并没有重新渲染:就是state.todos建立的索引没有变,所以要想重新渲染需要:todo:[...state.todos]
代码片2中:
这是一个陷阱,即使 state.todos
和 getVisibleTodos(state)
同样不再发生变化,但是因为每次 mapStateToProps
返回结果{ data: {...} }
中的 data
都创建新的(字面量)对象,导致浅对比总是失败, App
依然会再次渲染
同样,我在学习笔记(3)-react-redux使用问题中也记录过类似的问题。
至此我们知道了:
我们已经知道了 react-redux 中也
会对根状态进行浅对比,如果引用发生了改变,才重新渲染组件。 所以当状态需要发生更改时,务必让相应的 reducer 函数始终返回新的对象!修改原有对象的属性值然后返回不会触发组件的重新渲染!(所以使用扩展运算符/object.assign/immutable.js)
因此我们可以总结一下connect的作用
1、connect是一个高阶组件,将stateProps,dispatchProps,还有ownProps合并起来,一起传给wrappedComponent,将其作为返回的新组件的子组件进行渲染。
2、connect其实帮我们做了性能的优化的,当state跟props发生改变时,selector如果变化,最终计算出来的结果会进行一次浅对比(areStatePropsEqual = shallowEqual,)来设置shouldComponentUpdate防止重复渲染