-
React入口,将使用react定制的组件
<app/>
渲染到指定的element。ReactDOM.render(<App />, document.getElementById('root'));
-
可以看作是一个自定义的
HTML
标签(如同<button/>
),在react里我们称之为组件,开发react实际上是开发组件的过程。 -
组件的写法
组件有两种写法,函数式写法和class式写法。
先看函数式写法,和普通的JavaScript函数写法唯一的区别是返回值是一些标签组合构成的组件,其中
<>
内的内容按照HTML
规则解析,{}
内的内容按照JavaScript
的规则进行解析。import React from "react"; export function ShowMessage() { let message = "this is for show some message." return <p>{message}</p> }
以上组件不带有参数,下面看带参数组件的写法:
import React from "react"; export function ShowMessageWithProps(props) { const showMessage = () => { alert('Followed ' + props.user); }; const handleClick = () => { setTimeout(showMessage, 3000); }; return ( <button onClick={handleClick}>Follow</button> ); }
可以看出只需要在函数参数中申明
props
即可在函数内部任意使用props
内部携带的参数。使用方法与普通的标签使用方法相同:<ShowMessageWithProps user="xiaojun"/>
class式写法如下,继承React.Component后重载render函数。
class Demo extends React.Component { constructor(props) { super(props); this.state = { text: props.initialValue || 'placeholder' }; // 手动绑定this this.handleChange = this.handleChange.bind(this); } handleChange(event) { this.setState({ text: event.target.value }); } render() { return ( <div> <input value={this.state.text} onChange={this.handleChange}/> </div> ); } } Demo.propTypes = { initialValue: React.PropTypes.string }
纯函数组件的特点:
- 组件不会被实例化,整体渲染性能得到提升
- 组件不能访问this对象
- 组件无法访问生命周期的方法
- 无状态组件只能访问输入的props,无副作用
纯函数组件被鼓励在大型项目中尽可能以简单的写法来分割原本庞大的组件,未来React也会这种面向无状态组件在譬如无意义的检查和内存分配领域进行一系列优化,所以只要有可能,尽量使用无状态组件
关于函数组件和类组件的差别详细可参考React函数组件和类组件的差异。
-
组件参数的类型检查与默认参数
从实例代码中可以看出,只需要设置类组件中的propTypes和defaultProps对象即可实现类型检查和设置默认参数。
import React from "react"; import PropTypes from "prop-types"; export class Dashboard extends React.Component{ render() { return <p>{this.props.text}</p> } } Dashboard.propTypes= { text: PropTypes.string }; Dashboard.defaultProps = { name: 'Stranger' };
-
类组件的生命周期
组件的生命周期分为三个状态:
- Mounting:已插入真实 DOM
- Updating:正在被重新渲染
- Unmounting:已移出真实 DOM
React 根据生命周期的不同阶段提供了以下钩子函数,若需要在指定阶段执行一些定制化操作,在类组件中重载相应阶段的钩子函数即可。
- componentWillMount()
- componentDidMount()
- componentWillUpdate(object nextProps, object nextState)
- componentDidUpdate(object prevProps, object prevState)
- componentWillUnmount()
- componentWillReceiveProps(object nextProps):已加载组件收到新的参数时调用
- shouldComponentUpdate(object nextProps, object nextState):组件判断是否重新渲染时调用
-
Hook
回顾3中的函数式组件的写法,我们可以看到函数式组件是无状态的(依靠外部传值给组件,组件可以看成是该值的一种函数映射,无状态我理解是外部传的值与组件的状态是一一对应的关系),若我们想赋予函数组件状态该怎么办?官方为我们提供了hook特性,它可以让我们在不编写类组件的情况下使用state以及其它的React特性。
import React, {useState} from "react"; export function Counter() { let [count, setCount] = useState(0) return <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> }
useState
传入state的初始值,返回一个数组。该数组的第一项是stateXXX
的当前值,第二项为设置该state的函数,约定为setXXX
。其它hook可参考阮一峰大神的教程: React Hooks 入门教程:
- useContext() 共享状态钩子
- useReducer() Action钩子
- useEffect() 副作用钩子
- useRef()给标签创建引用
-
Redux
前端应用的本质是一个用来展示数据的函数,我提供什么样的store(数据),你给我展示什么样的view。点击view上的按钮能够通过dispatcher触发action,reducer根据action来计算新的store,最后更新view。
Redux三大原则
-
单一数据源
整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
这让同构应用开发变得非常容易。来自服务端的 state 可以在无需编写更多代码的情况下被序列化并注入到客户端中。由于是单一的 state tree ,调试也变得非常容易。在开发中,你可以把应用的 state 保存在本地,从而加快开发速度。此外,受益于单一的 state tree ,以前难以实现的如“撤销/重做”这类功能也变得轻而易举。
-
State只读
唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
这样确保了视图和网络请求都不能直接修改 state,相反它们只能表达想要修改的意图。因为所有的修改都被集中化处理,且严格按照一个接一个的顺序执行,因此不用担心竞态条件(race condition)的出现。 Action 就是普通对象而已,因此它们可以被日志打印、序列化、储存、后期调试或测试时回放出来。
-
使用纯函数来执行修改
为了描述 action 如何改变 state tree ,你需要编写 reducers。
Reducer 只是一些纯函数,它接收先前的 state 和 action,并返回新的 state。刚开始你可以只有一个 reducer,随着应用变大,你可以把它拆成多个小的 reducers,分别独立地操作 state tree 的不同部分,因为 reducer 只是函数,你可以控制它们被调用的顺序,传入附加数据,甚至编写可复用的 reducer 来处理一些通用任务,如分页器。
学习文档:Redux 中文文档
小问题:表单输入信息,按钮提交信息并显示在文本框,如何使用函数式组件的思想来设计组件?
表单信息和提交按钮定义到同一个组件中,点击按钮将调用组件中button的onClick函数。函数式组件是无状态的,只能够接受数据,那么如何将数据存储到中心的store呢?
探索方案一,如下代码所示直接在onClick中发送action。但是很容易看出这种方式并不是很好的设计,组件与action以及发送的数据有着强力的耦合,函数不够纯,复用性不好。
import React, {useRef} from "react";
import store from "../store";
import {CLICK} from "../actions/actions";
const InputBox = () => {
const thisInput = useRef();
const getInput = () => thisInput.current.value;
const handleInput = () => store.dispatch({type: CLICK, text: getInput()})
return <div>
<input ref={thisInput}/>
<button onClick={handleInput}>Click</button>
</div>
}
export default InputBox
探索方案二,如下代码所示,在props属性中传入函数,在onClick中调用该函数,并将需要保存的值作为参数传入该函数。这样设计的好处是更加纯粹,可复用性好。
import React, {useRef} from "react"
const InputBox = (props) => {
const thisInput = useRef();
const getInput = () => thisInput.current.value;
const handleInput = () => props.handleClick(getInput())
return <div>
<input ref={thisInput}/>
<button onClick={handleInput}>Click</button>
</div>
}
export default InputBox
在父组件中调用,发送消息的逻辑放在父组件中:
const handleClick = (text) => {
store.dispatch({type: CLICK, text: text})
}
const render = () => {
let text = store.getState().toString();
ReactDOM.render(
<div>
<InputBox handleClick={handleClick}/>
<ShowMessage text={text}/>
</div>,
document.getElementById('root'));
}