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 标签在书写时需要改为
className和htmlFor
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)
}
代理的原因:
- 代理之后内部可以做一些优化,绑定事件过多的话,大型项目会出问题,原生还需要手动销毁;
- 跨端兼容性较好
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的时候,组件每次重新渲染
解决:
- 不要在 render 中写箭头函数
- 通常将上一步写在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