http://huziketang.mangojuice.top/books/react/ react小书
这是我读React 小书的一些笔记
开始
只有改变了state状态,也就是使用了this.setState(...)才能导致重新调用render。
记住,只要你要写 React.js 组件,那么就必须要引入这两个东西:React和组件继承 Component 类
import React, { Component } from 'react'
render的用法:
元素变量:
render () {
const isGoodWord = true
const goodWord = <strong> is good</strong>
const badWord = <span> is not good</span>
return (
<div>
<h1>
React 小书
{isGoodWord ? goodWord : badWord}
</h1>
</div>
)
}
条件返回:
render () {
const isGoodWord = true
return (
<div>
<h1>
React 小书
{isGoodWord
? <strong> is good</strong>
: <span> is not good</span>
}
</h1>
</div>
)
}
ReactDOM 可以帮助我们把 React 组件渲染到页面上去,没有其它的作用了。
自定义的组件都必须要用大写字母开头,普通的 HTML 标签都用小写字母开头。
on* 的事件监听只能用在普通的 HTML 的标签上,而不能用在组件标签上。
<div></div>属于HTML 的标签,<Mytitle></Mytitle>属于组件标签
获取点击实例:e.target
class Title extends Component {
handleClickOnTitle (e) {
console.log(e.target.innerHTML)
}
render () {
return (
<h1 onClick={this.handleClickOnTitle}>React 小书</h1>
)
}
}
点击获取实例:this,传参
class Title extends Component {
handleClickOnTitle (word, e) {
console.log(this, word)
}
render () {
return (
<h1 onClick={this.handleClickOnTitle.bind(this, 'Hello')}>React 小书</h1>
)
}
}
setState 接受函数参数:
你会发现两次打印的都是 false,即使我们中间已经 setState 过一次了。这并不是什么 bug,只是 React.js 的 setState 把你的传进来的状态缓存起来,稍后才会帮你更新到 state 上,所以你获取到的还是原来的 isLiked。
handleClickOnLikeButton () {
console.log(this.state.isLiked)
this.setState({
isLiked: !this.state.isLiked
})
console.log(this.state.isLiked)
}
所以如果你想在 setState 之后使用新的 state 来做后续运算就做不到了
可以这样做:
handleClickOnLikeButton () {
this.setState((prevState) => {
return { count: 0 }
})
this.setState((prevState) => {
return { count: prevState.count + 1 } // 上一个 setState 的返回是 count 为 0,当前返回 1
})
this.setState((prevState) => {
return { count: prevState.count + 2 } // 上一个 setState 的返回是 count 为 1,当前返回 3
})
// 最后的结果是 this.state.count 为 3
}
上面我们进行了三次 setState,但是实际上组件只会重新渲染一次,而不是三次,这就是setState的合并,并不需要担心多次进行 setState 会带来性能问题。
默认配置 defaultProps:
如果this.props.likedText没有被定义的时候,就会去取defaultProps.likedText
static defaultProps = {
likedText: '取消',
unlikedText: '点赞'
}
props 不可变:
handleClickOnLikeButton () {
this.props.likedText = '取消'
}
这样的代码会引发报错,props是不可变的,如果 props 渲染过程中可以被修改,那么就会导致这个组件显示形态和行为变得不可预测,这样会可能会给组件使用者带来困惑。
props 的主要作用是让使用该组件的父组件可以传入参数来配置该组件。它是外部传进来的配置参数,组件内部无法控制也无法修改。除非外部组件主动传入新的 props,否则组件的 props 永远保持不变。
state和props
尽量少地用 state,尽量多地用 props。
没有 state 的组件叫无状态组件(stateless component),设置了 state 的叫做有状态组件(stateful component)。因为状态会带来管理的复杂性,我们尽量多地写无状态组件,尽量少地写有状态的组件。这样会降低代码维护的难度,也会在一定程度上增强组件的可复用性。
渲染列表:
class Index extends Component {
render () {
return (
<div>
{users.map((user) => {
return (
<div>
<div>姓名:{user.username}</div>
<div>年龄:{user.age}</div>
<div>性别:{user.gender}</div>
<hr />
</div>
)
})}
</div>
)
}
}
更简化:
class User extends Component {
render () {
const { user } = this.props
return (
<div>
<div>姓名:{user.username}</div>
<div>年龄:{user.age}</div>
<div>性别:{user.gender}</div>
<hr />
</div>
)
}
}
class Index extends Component {
render () {
return (
<div>
{users.map((user) => <User key={user.id} user={user} />)}
</div>
)
}
}
记得用key,没有id的话可以用计数器来表示
{users.map((user, i) => <User key={i} user={user} />)}
想想使用key的目的,这种方法只是掩耳盗铃
本着React.js 团队的做法,把事情搞复杂一些,提高数据修改的门槛。解决“模块(组件)之间需要共享数据”,和“数据可能被任意修改导致不可预料的结果”之间的矛盾。是action,dispatch出现的重要原因
纯函数:
1.函数的返回结果只依赖于它的参数。
2.函数执行过程里面没有副作用。
实例1:
const a = 1
const foo = (b) => a + b
foo(2) // => 3
这个不是纯函数,因为返回的结果不止和参数有关还和外部变量有关
实例2:
const a = 1
const foo = (obj, b) => {
obj.x = 2
return obj.x + b
}
const counter = { x: 1 }
foo(counter, 2) // => 4
counter.x // => 2
foo函数的执行对外部的 counter 产生了影响(obj.x产生了影响),它产生了副作用
除了修改外部的变量,一个函数在执行过程中还有很多方式产生外部可观察的变化,比如说调用 DOM API 修改页面,或者你发送了 Ajax 请求,还有调用 window.reload 刷新浏览器,甚至是 console.log 往控制台打印数据也是副作用。
为什么要煞费苦心地构建纯函数?因为纯函数非常“靠谱”,执行一个纯函数你不用担心它会干什么坏事,它不会产生不可预料的行为,也不会对外部产生影响。不管何时何地,你给它什么它就会乖乖地吐出什么。如果你的应用程序大多数函数都是由纯函数组成,那么你的程序测试、调试起来会非常方便。
es6 扩展运算符赋值
一般我们不会直接改变state而是使用浅复制,创建一个NewState
像这样:
let newAppState1 = { // 新建一个 newAppState1
...appState , // 复制 newAppState1 里面的内容
title: { // 用一个新的对象覆盖原来的 title 属性
...appState .title, // 复制原来 title 对象里面的内容
color: "blue" // 覆盖 color 属性
}
}
它的好处是什么?我们为什么要这么做?
appState !== newAppState // true,两个对象引用不同,数据变化了,重新渲染
appState.title !== newAppState.title // true,两个对象引用不同,数据变化了,重新渲染
appState.content !== appState.content // false,两个对象引用相同,数据没有变化,不需要重新渲染
高阶函数:
高阶组件就是一个函数,传给它一个组件,在里面通过方法和传入的参数装饰一下,返回一个新的组件。
redux:
要注意的是,Redux 和 React-redux 并不是同一个东西。Redux 是一种架构模式(Flux 架构的一种变种),它不关注你到底用什么库,你可以把它应用到 React 和 Vue,甚至跟 jQuery 结合都没有问题。而 React-redux 就是把 Redux 这种架构模式和 React.js 结合起来的一个库,就是 Redux 架构在 React.js 中的体现。
reducer是纯函数
redux的使用套路总结下来就是:
// 定一个 reducer
function reducer (state, action) {
/* 初始化 state 和 switch case */
}
// 生成 store
const store = createStore(reducer)
// 监听数据变化重新渲染页面
store.subscribe(() => renderApp(store.getState()))
// 首次渲染页面
renderApp(store.getState())
// 后面可以随意 dispatch 了,页面自动更新
store.dispatch(...)
connect
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from './react-redux'
class Header extends Component {
static propTypes = {
themeColor: PropTypes.string
}
render () {
return (
<h1 style={{ color: this.props.themeColor }}>React.js 小书</h1>
)
}
}
const mapStateToProps = (state) => {
return {
themeColor: state.themeColor
}
}
Header = connect(mapStateToProps)(Header)
export default Header
我们可以这么理解这句话:
Header = connect(mapStateToProps)(Header)
Header中包含对共享数据的操作,以及根据state进行后续组件渲染,connect是一个包装Header组件的装饰函数,它需要这个原组件本身和与组件相关的state数据作为参数。
还要传一个mapDispatchToProps作为参数
const mapDispatchToProps = (dispatch) => {
return {
onSwitchColor: (color) => {
dispatch({ type: 'CHANGE_COLOR', themeColor: color })
}
}
}
ThemeSwitch = connect(mapStateToProps, mapDispatchToProps)(ThemeSwitch)
挂载阶段的生命周期:
我们把 React.js 将组件渲染,并且构造 DOM 元素然后塞入页面的过程称为组件的挂载(这个定义请好好记住)
-> constructor()
-> componentWillMount()
-> render()
// 然后构造 DOM 元素插入页面
-> componentDidMount()
一些组件启动的动作,包括像 Ajax 数据的拉取操作、一些定时器的启动等,就可以放在 componentWillMount 里面进行
这里有段计时器的代码:
class Clock extends Component {
constructor () {
super()
this.state = {
date: new Date()
}
}
componentWillMount () {
clearInterval(this.timer);//记得先清除定时器,避免重新渲染时定时器还在
this.timer = setInterval(() => {
this.setState({ date: new Date() })
}, 1000)
}
...
}
更新阶段的组件生命周期:
1.shouldComponentUpdate(nextProps, nextState):你可以通过这个方法控制组件是否重新渲染。如果返回 false 组件就不会重新渲染。这个生命周期在 React.js 性能优化上非常有用。
2.componentWillReceiveProps(nextProps):组件从父组件接收到新的 props 之前调用。
3.componentWillUpdate():组件开始重新渲染之前调用。
4.componentDidUpdate():组件重新渲染并且把更改变更到真实的 DOM 以后调用。
ref:
React.js 并不能完全满足所有 DOM 操作需求,有些时候我们还是需要和 DOM 打交道。比如说你想进入页面以后自动 focus 到某个输入框,你需要调用 input.focus() 的 DOM API,比如说你想动态获取某个 DOM 元素的尺寸来做后续的动画,等等
class AutoFocusInput extends Component {
componentDidMount () {
this.input.focus()
}
render () {
return (
<input ref={(input) => this.input = input} />
)
}
}
ReactDOM.render(
<AutoFocusInput />,
document.getElementById('root')
)
然后我们就可以在 componentDidMount 中使用这个 DOM 元素,并且调用 this.input.focus() 的 DOM API。整体就达到了页面加载完成就自动 focus 到输入框的功能
也可以给组件加:
<Clock ref={(clock) => this.clock = clock} />
但ref这个属性能不用就不要用
props.children:
它定义了一种外层结构形式,然后你可以往里面塞任意的内容。有点类似于DOM操作中的append()
class PropsChilden extends Component {
render() {
const childer = this.props.children;
return (
<div>
{childer.map((item,index)=>{
return <div key={index}>{item}</div>;
})}
</div>
);
}
}
export default PropsChilden;
用的时候:
<PropsChilden>
<span>rick</span>
<span>zhangamie</span>
<span>react</span>
22132132
</PropsChilden>
但是这样是不可以的:
<PropsChilden>
[1,2,3,4]
</PropsChilden>
因为传过去的this.props.children是string类型,map函数自然会报错
动态样式:
style接受的是一个对象,要用驼峰命名方式,font-size写做fontSize
<h1 style={{fontSize: '12px', color: this.state.color}}>React.js 小书</h1>
dangerouslySetInnerHTML
class Editor extends Component {
constructor() {
super()
this.state = {
content: '<h1>React.js 小书</h1>'
}
}
render () {
return (
<div className='editor-wrapper'>
{this.state.content}
</div>
)
}
}
当你写这些以为会得到一个加粗黑体的一行字吗?不,你得到的是这个:

你应该这么写:
render () {
return (
<div
className='editor-wrapper'
dangerouslySetInnerHTML={{__html: this.state.content}} />
)
}
但是设置 innerHTML 可能会导致跨站脚本攻击(XSS),这个属性不必要的情况就不要使用。
PropTypes 和组件参数验证
在做大型应用的时候有PropTypes写作规范是很有益的,因为js是弱类型语言,是允许let a = {number:1};a = 1;这样的代码的。项目越大有些隐晦的bug会越不好找。我们需要一个规范及时矫正偏差
安装:npm install --save prop-types
import React, { Component } from 'react'
import PropTypes from 'prop-types'
class Comment extends Component {
static propTypes = {
comment: PropTypes.object
}
render () {
const { comment } = this.props
return (
<div className='comment'>
<div className='comment-user'>
<span>{comment.username} </span>:
</div>
<p>{comment.content}</p>
</div>
)
}
}
这段代码是说:comment字段必须是一个object类型,如果你传入其他类型就会报错

也有该字段未赋值,undefined,我们可以这样写:
static propTypes = {
comment: PropTypes.object.isRequired
}
网友评论