一、react 基本知识
- JSX基本使用
- setState ***
- 生命周期
- 条件
- 列表
- 事件
- 表单
- 组件 跟 props
1. jsx语法
-
语法:html 属性名请使用小驼峰(camelCase)写法
const element = <h1>Hello, world!</h1>; // 函数形式,return为最终拿到的html结构 function Element(props) { return ( <h1>Hello, world!</h1> ) }
-
小括号括:可以将 HTML 语句写为多行以增加可读性,用小括号括起来可以防止自动插入分号导致的错误
const element = ( <h1> Hello, {formatName(user)}! </h1> );
-
可用表达式,如 for 和 if 中:
function getGreeting(user){ if (user){ return <h1>Hello, {formatName(user)}!</h1>; } return <h1>Hello, Stranger.</h1>; }
-
babel 会编译 JSX 成 React.createElement() 的参数调用
const element = ( <h1 className="greeting"> Hello, world! </h1> ); // 编译为以下形式 const element = React.createElement( 'h1', {className: 'greeting'}, 'Hello, world!' ); // React.createElement() 会生成这样一个对象(React 元素): const element = { type: 'h1', props: { className: 'greeting', children: 'Hello, world' } };
2. 元素渲染: 通过 ReactDOM.render() 方法很轻松的把内容渲染到了目标容器上。
- ReactDOM.render(“要渲染的jsx内容”, “渲染到哪个节点上做绑定”);
-
定时更新时钟的例子
function Tick(){ const element = ( <div> <h1>Hello, world!</h1> <h2>It is {new Date().toLocaleTimeString()}.</h2> </div> ); ReactDOM.render( element, document.getElementById('root') ); } setInterval(Tick, 1000); // 组件方式(jsx语法):重写上面时钟,时间作为变量动态传入jsx结构中 function Clock(props){ return ( <div> <h1>Hello, world!</h1> <h2>It is {props.date.toLocaleTimeString()}.</h2> </div> ); } function Tick(){ ReactDOM.render( <Clock date={new Date()} />, document.getElementById('root') ); } setInterval(Tick, 1000);
3.setState*
-
不可变值
-
不可变值(纯函数)— 数组,不能直接对 this.state.list 进行 push 、pop、splice等,这样违反不可变值
count list5Copy = this.state.list5.slice(); list5Copy.splice(2, 0, "a"); this.setState({ list1: this.state.list1.concat(100), //追加 list2: [...this.state.list2, 100], //追加 list3: this.state.list3.slice(0, 3), //截取 list4: this.state.list4.filter(item => {item => item > 100}), //筛选 list5: list5Copy //其他操作 })
-
不可变值— 对象,不能直接对 this.state.obj 进行属性设置,这样违反不可变值
this.setState({ obj1: Object.assign({}, this.state.obj1, {a: 100}), obj2: {...this.state.obj2, a: 100} })
-
-
可能是异步
-
异步更新回调
this.setState({ count: this.state.count + 1 }, () => { //跟 Vue $nextTick 类似 })
-
直接使用setState是异步更新状态值。(主线程执行完才执行异步线程)
this.setState({ count: this.count + 1 }) console.log("count的值:" + count); //注:界面看到count已经加1了,但是控制台打印出来的确是原始的值。
-
在setTimeout中使用是同步的
-
在自定义的Dom事件中,setStateshi 同步的
clickHandle = () => { this.setState({ count: this.count + 1 }) } componentDidMount() { // 自定义的Dom事件,setState 是同步的 document.body.addEventListener("click", this.clickHandle) }
-
-
可能会被合并
-
setState传入对象,会被合并
constructor(props) { this.state = { count:0 } } this.setState({ count: this.state.count + 1 }) this.setState({ count: this.state.count + 1 }) this.setState({ count: this.state.count + 1 }) // 执行结果:1,因为setState异步更新,当要异步更新的时候取到count都是为加1的值。
-
传入函数,不会被合并,执行结果 是 + 3
constructor(props) { this.state = { count:0 } } this.setState((prevState, props) => { return { count: prevState.count + 1 } }) this.setState((prevState, props) => { return { count: prevState.count + 1 } }) this.setState((prevState, props) => { return { count: prevState.count + 1 } })
-
4.组件的生命周期
- 单组件生命周期(对比vue)
- react挂载时:constructor —> render, 渲染jsx —> React 更新 DOM 跟refs —> component-DidMount,一般用来访问ajax
- react更新时:setState( ) —> shouldComponent-Update,决定是否继续渲染 —> render,渲染jsx —> React 更新 DOM 跟refs —> componentDidUpdate
- react卸载时:componentWill-Unmount, 一般用来卸载定时器,清除挂载的Dom事件
- vue挂载时:beforeCreate —> created —> beforeMount —> mounted,一般用来访问ajax
- 父子组件生命周期,和Vue的一样
5. 组件:state、lifecycle、事件、条件判断、列表循环、表单绑定变量
- React 要求所有组件函数都必须是纯函数
- 如果一个函数执行过程中不改变其参数,也不改变其外部作用于参数,当相同的输入总能得到相同的值时,我们称之这样的函数为纯函数。
- 组件名的首字母大写。
render
方法的返回值描述了你希望在屏幕上看到的内容
-
时间功能用react组件方式实现:
- 新建一个类,类名同组件函数名Clock,并继承自 React.Component
- 给该类添加一个方法 render(/无参数/), 放置原来clock的jsx代码
- 将 render 方法中的 props 换为 this.props,父组件给子组件值也是通过属性传递。
class Clock extends React.Component { render(){ return ( <div> <h1>Hello, world!</h1> <h2>It is {this.props.date.toLocaleTimeString()}.</h2> </div> ); } }
-
利用state,lifecycle(生命周期函数):实现时钟组件自动更新时间功能
class Clock extends React.Componet { constructor(props) { super(props); //必须写, 固定格式 this.state = { date: new Date() } } tick(){ this.setState({ date: new Date() }); } // 渲染(render方法)完成时立即执行 componentDidMount(){ this.timerID = setInterval( () => this.tick(), 1000 ); } // render 的内容即将被移除前执行 componentWillUnmount(){ clearInterval(this.timerID); } render() { return( <div> <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ) } }
-
组件中的事件:
- 事件名使用小驼峰写法,而不是全小写,例如:onclick 写作 onClick
- 注册事件使用花括号表达式替代原有函数写法
- 无法通过事件函数
return false
的方式阻止默认事件,必须显式的调用 preventDefault() - *****注:this指向要注意,不是箭头函数都容易出错
class Button extends React.Component { constructor(){ super(); this.name = "Bob"; // 一般用这种方式修正事件函数 this 绑定:写个跟事件函数同名的属性click,赋值为事件函数并修改其this指向 this.click = this.click.bind(this); } click(){ console.log(`hello ${this.name}`); } render(){ return ( <raw> <button onClick={this.click}>Click me</button> </raw> ); } }
-
组件中条件渲染
class LoginControl extends React.Component { constructor(props) { super(props); this.state = { isLoggedIn: false }; } render() { const { isLoggedIn } = this.state; // 根据isLoggedIn的值,指定渲染某个按钮。 const button = isLoggedIn ? <button onClick={() => { this.setState({isLoggedIn: false}); }}>注销</button> : <button onClick={() => { this.setState({isLoggedIn: true}); }}>登录</button>; return ( <div> <h1> { isLoggedIn ? 'Welcome back!' : 'Please sign up.' } </h1> {button} </div> ); } } ReactDOM.render( <LoginControl />, document.getElementById('root') );
-
列表循环
- key 是一个字符串,并且在该列表中唯一
- key 的值只是给 React 起到类似暗示的作用,不会真正的传递给 dom, 方便react更新页面,所以如果你需要使用 key 的值,应使用一个其它变量传递该值
const data = [1, 2, 3, 4, 5]; const listItems = data.map((item) => <li key={number.toString()}>{item}</li> ); ReactDOM.render( <ul>{listItems}</ul>, document.getElementById('root') );
-
组件中表单:不同于vue,要自己写表单监听事件去动态修改绑定在表单上变量的值。
class Reservation extends React.Component { constructor(props) { super(props); this.state = { isGoing: true, numberOfGuests: 2 }; this.handleInputChange = this.handleInputChange.bind(this); } // 根据绑定在表单的name去动态修改指定name表单的变量值。 handleInputChange(event) { const { target } = event; const value = target.type === 'checkbox' ? target.checked : target.value; const { name } = target; this.setState({ [name]: value }); } render() { return ( <form> <label> Is going: <input name="isGoing" //要跟绑定在表单的state.isGoing同名,才能动态修改绑定表单的变量值 type="checkbox" checked={this.state.isGoing} onChange={this.handleInputChange} /> </label> <br /> <label> Number of guests: <input name="numberOfGuests" type="number"s value={this.state.numberOfGuests} onChange={this.handleInputChange} /> </label> </form> ); } }
-
状态提升(父子组件通讯):多个组件需要共享的状态提升到它们最近的父组件state上,共享的状态,跟修改共享状态的方法一起通过props分发给各个子组件,在子组件使用父组件上的共享状态。
//1.在父组件中定义共享状态content,并将其传给各个子组件。 class AllInput extends React.Component { constructor(props) { super(props) // 在父组件中添加 state 对象,用于保存数据 this.state = { content: '' } this.handleContentChange = this.handleContentChange.bind(this) } // 定义修改 state 的方法,通过 props 传递给子组件使用 // 接收一个参数(新的数据) handleContentChange(newContent) { this.setState({ content: newContent }) } render() { // 通过 props 将 state 和修改 state 的方法都传递给子组件 return ( <div> <Input content={ this.state.content } onContentChange={ this.handleContentChange }/> <br /><br /> <Input content={ this.state.content } onContentChange={ this.handleContentChange }/> </div> ) } } //2.子组件中使用父组件传来的共享状态content,跟修改content的函数 class Input extends React.Component { constructor(props) { super(props) // 数据可以不再保存在子组件的 state 当中 this.handleChange = this.handleChange.bind(this) } handleChange(e) { // 通过 props 获取父组件的 setState(修改数据的方法) this.props.onContentChange(e.target.value) } render() { // 通过 props 获取父组件的 state(数据) return ( <input type='text' value={ this.props.content } onChange={ this.handleChange } /> ) } }
二、React 高级特性(1)
- 函数组件,非受控组件,Portals,context,异步组件,性能优化,高阶组件HOC,Render Props
1. 函数组件。
-
纯函数,输入props,输出JSX
-
没有实例,没有生命周期,没有state
-
不能扩展其他方法
// 函数组件 function List(props) { const {list} = this.props; return <ul>{list.map((item, index) => { return <li key={item.id}> <span>{item.title}</span> </li> })}</ul> }
2.非受控组件
-
ref
-
defaultValue defaultChecked
-
手动操作DOM元素
// 非受控组件, 输入框的值不会影响到state的值,state的值只是用来初始化input的默认值。 class App extends React.Component { constructor(props) { super(props); this.state = { name: "jiangyf", flag: true } this.nameInputRef = React.creatRef(); //创建ref } render() { return <div> // 使用defaultValue,而不是value <input defaultValue={this.state.name} ref={this.nameInputRef}> <button onClick={this.alertName}></button> </div> } alertName = () => { const elem = this.nameInputRef.current; //通过 ref 获取 Dom 节点 alert(elem.value) // 不是this.state.name } } // 受控组件 <input value={this.state.name} onChange={this.onInputChange}></input> onInputChange = (e)=> { this.setState({ name: e.target.value }) }
-
非受控组件使用场景
- 必须手动操作Dom元素,setState实现不了
- 文件上传
- 某些富文本编辑器,要传入DOM元素
3. Portals
-
可以把组件渲染到指定节点中,可以让组件渲染到父组件以外层级
export default class Portals extends Component { render() { return ( <div title='我是父组件'> <p>我想出现在root中</p> <Test/> <div> ) } } function Test() { return ReactDom.createPortal( <ChildA/>, document.getElementById("container"); ) } function ChildA() { return <p>我是childA</p>; } // 渲染结果 <div id="root"> <div title='我是父组件'> <p>我想出现在root中</p> <div> <div id="container"> <p>我是childA</p> </div> </div>
4.context
-
场景:公共信息(语音,主题)如何传递给每个组件?
-
用props太繁琐
-
用redux小题大做
// 1.创建Context 填入默认值(任何一个 js 变量) const ThemeContext = React.createContext("light"); class TestApp extentds React.Component { constructor(props) { super(props); this.state = { theme: "light" } } render() { // 2. 生产context:ThemeContext.Provider标签的value提供Context内容 return <ThemeContext.Provider value={this.state.theme}> <Toolbar></Toolbar> <button onClick={this.changeTheme}></button> </ThemeContext.Provider> } // 修改 context内容 changeTheme = () => { this.setState({ theme: this.state.theme == 'light' ? 'dark' : 'light' }) } } function ToolBar(props) { return ( <div> <ThemeButton></ThemeButton> <ThemeLink></ThemeLink> </div> ) } // 3. class组件如何使用context? class ThemedButton extentds React.Component { render() { // react 会往上找到最近的ThemeContext.Provider中的value值。 const theme = this.context; return <div> <p>button theme is {theme}</p> </div> } } // 指定class组件静态属性contextType 读取当前的 ThemeContext. ThemedButton.contextType = ThemeContext; // 4. 函数组件如何使用context? function ThemeLink(props) { // 函数组件可以用Consumer,用this.context会报错,因为没有实例。 return <ThemeContext.Consumer> {value => <p>link theme is {value}</p>} </ThemeContext.Consumer> }
### 5.react如何异步加载组件
* ```react
const ContextDemo = React.lazy(() => import("./ContextDemo"));
class TestApp extends React.Component {
constructor(props) {
super(props);
}
reder() {
// 异步加载组件必须配合 <React.Suspense fallback={}></React.Suspense>一起使用
return <div>
<React.Suspense fallback={<div>Loading</div>}>
<ContextDemo/>
</React.Suspense>
</div>
}
}
三、React 高级特性(2)
1. 性能优化-SCU
- react 父组件有更新,子组件则无条件也更新,如有需要可通过SCU优化。
- SCU 需要的时候才优化
-
SCU基本用法,默认返回 true,即默认重新渲染所有子组件。
shouldComponentUpdate(nextProps, nextState) { if(nextState.count !== this.state.count) { return true; // 可以渲染 } return false; // 不重复渲染 }
-
SCU 一定要配合 不可变值 使用
import _ from 'lodash'; class TodoListDemo extends React.Component { constructor(props) { super(props) this.state = { list: [ { id: 'id-1', title: '标题1'}, { id: 'id-2', title: '标题2'},] } } render() { return <div> <Input submitTitle={this.onSubmitTitle}/> <List list={this.state.list}/> </div> } onSubmitTitle = (title) => { // 正确的用法 this.setState({ list: this.state.list.concat({ id: `id-${Date.now()}`, title }) }) // 错误用法,这样写会导致shouldComponentUpdate取到的nextProps.list 跟 this.props.list 一样导致无法重新渲染。 // this.state.list.push({ // id: `id-${Date.now()}`, // title // }) // this.setState({ // list: this.state.list // }) } } class List extends React.Component { constructor(props) { super(props) } render() { const { list } = this.props return <ul>{list.map((item, index) => { return <li key={item.id}> <span>{item.title}</span> </li> })}</ul> } // 增加 shouldComponentUpdate shouldComponentUpdate(nextProps, nextState) { // _.isEqual 做对象或者数组的深度比较(一次性递归到底) if (_.isEqual(nextProps.list, this.props.list)) { // 相等,则不重复渲染 return false } return true // 不相等,则渲染 } }
2. 性能优化-PureComponent 和 memo 浅比较,实现组件是否重新渲染。
- PureComponent跟memo是浅比较。
- React.PureComponent是基于浅比较,所以只要属性值是引用类型,但是修改后的值变了,但是地址不变,也不会重新渲染。
- 浅比较已经适用大部分情况(尽量不要做深度比较)。
-
React.PureComponent基本用法
class List extends React.PureComponent { constructor(props) { super(props) } render() { const { list } = this.props // 如果title跟id没有改变的话就不会重写渲染 return <ul>{list.map((item, index) => { return <li key={item.id}> <span>{item.title}</span> </li> })}</ul> } // PureComponent 里有 shouldComponentUpdate 函数的话,直接使用 shouldComponentUpdate 的结果作为是否更新的依据,没有 shouldComponentUpdate 函数的话,才会去判断是不是 PureComponent ,是的话再去做 shallowEqual 浅比较。 }
-
React.memo基本用法
// 函数组件 function MyComponent(props) { } // 编写类似SCU的函数,用来处理是否重新渲染 function areEqual(prevProps, nextProps) { } // 将函数组件,类似SCU函数一起传入React.mome,生成新的函数组件 export default React.memo(MyComponent, areEqual);
3. 性能优化-Immutable.js
-
彻底拥抱“不可变值”
-
基于共享数据(不是深拷贝),速度好
-
有一定学习和迁移成本,按需使用
const map1 = Immutable.map({ a: 1, b: 2, c: 3}); const map2 = Immutable.set( "b", 50); map1.get("b"); // 2 map2.get("b"); // 50
4. 高阶组件
-
基本用法
// 高阶组件不是一种功能,而不是一种模式 const HOCFactory = (Component) => { class HOC extends React.Component { // 在这个类内部定义多个组件的公共逻辑 render() { // 将处理好的数据传入到各个组件中各自渲染 return <Component {...this.props}/> } } return HOC } const TestComponent1 = HOCFactory(WrappedComponent1); const TestComponent2 = HOCFactory(WrappedComponent2);
-
例:利用高阶组件公用一份鼠标坐标信息,渲染出不同的展示效果。
const withMouse = (Component) => { class withMouseComponent extends React.Component { constructor(props) { super(props); this.state = { x: 0, y: 0 } } // 获取鼠标信息公共逻辑 getMouseInfos = (e) => { this.setState({ x: e.clientX, y: e.clientY }) } render() { return ( <div onMouseMove={this.getMouseInfos}> {/* 将获取到的鼠标信息,跟传入到高阶组价中的所有数据都传到各个组件中渲染 */} <Component {...this.props} mouse={this.state}></Component> </div> ) } } return withMouseComponent; } const App = (props) => { const a = props.a const { x, y } = props.mouse // 接收 mouse 属性 return ( <div style={{ height: '500px' }}> <h1>The mouse position is ({x}, {y})</h1> <p>{a}</p> </div> ) } export default withMouse(App) // 返回高阶函数