组件
将页面按照界面功能进行拆分,每一块界面都拥有自己的独立逻辑(组件),这样可以提高项目代码的可维护性和复用性。
如上图所示将这个卡片分为三个组件,那么当需要添加一个这样的卡片时,就可以复用这些组件,只需要稍稍改变一下这些组件的文字和一些参数就好了,大大提高了代码的复用性,并且若后期维护需要改变样式那么只需要改变对应组件的样式,所有的卡片的样式都会改变,大大提高了项目的可维护性。
类组件
使用class语法创建的组件就是类组件
- 类名首字母必需大写。 类中的构造器不是必须要写的,要对实例进行一些初始化的操作,如添加指定属性时才写。
- 必须继承React.Component父类
- 必需有render函数,返回 UI 结构,无渲染可返回 null
- 如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super是必须要调用的。
- 类中所定义的方法,都放在了类的原型对象上,供实例去使用。
<body>
<div id="root"></div>
<script type="text/javascript" src="./js/react.development.js"></script>
<script type="text/javascript" src="./js/react-dom.development.js"></script>
<script type="text/javascript" src="./js/babel.min.js"></script>
<script type="text/babel">
// 类组件
class Head extends React.Component {
render() {
return (
<div>
这是组件
</div>
)
}
}
// 根组件
class App extends React.Component {
render() {
return (
<div>
<Head />
<div style={{ height: 200, backgroundColor: 'red' }}>body</div>
</div>
);
}
}
// 渲染虚拟dom
ReactDOM.render(<App />, document.getElementById('root'));
</script>
</body>
注意
- render 是放在 MyComponent 的原型对象上,供实例使用。
- render 中的 this 是 MyComponent 的实例对象 <=> MyComponent组件实例对象。
执行了 ReactDOM.render(<MyComponent/>.......
之后,发生了什么?
- React解析组件标签,找到了MyComponent组件。
- 发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。
- 将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。
上面的这种把所有组件写在一个文件中会导致后期维护很麻烦,所以我们可以把每个组件单独的写在一个文件中,需要使用哪个组件就引入哪个组件。
状态
根据组件是否有状态可以将组件分为有状态组件和无状态组件。
- 无状态组件:组件本身不定义状态,没有组件生命周期,只负责 UI 渲染。
- 有状态组件:组件本身有独立数据,拥有组件生命周期,存在交互行为。
对比两种组件
无状态组件由于没有维护状态只做渲染,性能较好。有状态组件提供数据和生命周期,能力更强。
state
state是组件对象最重要的属性, 值是对象(可以包含多个key-value的组合),组件被称为"状态机"。使用state的时候通过this去访问即可,例如:this.state.xxx。下面的 Head 组件是有状态组件,Body 是无状态组件。
<body>
<div id="root"></div>
<script type="text/javascript" src="./js/react.development.js"></script>
<script type="text/javascript" src="./js/react-dom.development.js"></script>
<script type="text/javascript" src="./js/babel.min.js"></script>
<script type="text/babel">
// 类组件
class Head extends React.Component {
state = {
name: '孤城浪人',
age: 18
}
render() {
return (
<div>
姓名:{this.state.name}<br />
年龄:{this.state.age}
</div>
)
}
}
class Body extends React.Component {
render() {
return (
<div>
body
</div>
)
}
}
// 根组件
class App extends React.Component {
render() {
return (
<div>
<Head />
<div style={{ height: 200, backgroundColor: 'red' }}>body</div>
</div>
);
}
}
// 渲染虚拟dom
ReactDOM.render(<App />, document.getElementById('root'));
</script>
</body>
setState
状态(state)不可直接更改,状态必须通过 setState 方法进行更新,且更新是一种浅合并(只会合并相同 key 的值),不是替换。
setState 是“异步”的,多次 setState 会合并。
setState 是“异步”的,合并更新调用 setState 时,将要更新的状态对象,放到一个更新队列中暂存起来(没有立即更新)。如果多次调用 setState 更新状态,状态会进行合并,后面覆盖前面等到所有的操作都执行完毕,React 会拿到最终的状态,然后触发组件更新。这么做的目的是为了提升渲染性能。
- setState 接收一个对象
<body>
<div id="root"></div>
<script type="text/javascript" src="./js/react.development.js"></script>
<script type="text/javascript" src="./js/react-dom.development.js"></script>
<script type="text/javascript" src="./js/babel.min.js"></script>
<script type="text/babel">
// 类组件
// 根组件
class App extends React.Component {
state = {
count: 0
}
addCount = () => {
this.setState({ count: this.state.count + 2 });
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<div>
<div>
{this.state.count}
</div>
<button onClick={this.addCount}>add</button>
</div>
);
}
}
// 渲染虚拟dom
ReactDOM.render(<App />, document.getElementById('root'));
</script>
</body>
上面的例子中,点击按钮每次加 1,并不是 2,也不是 3 可以证明后面的 setState 覆盖了前边的。
- setState 接收函数
如果传给 setState 的是一个函数的话,那么这个函数可以拿到当前的 state。如果传了两个函数的话,那么在数据更新完后会执行第二个参数函数。
<body>
<div id="root"></div>
<script type="text/javascript" src="./js/react.development.js"></script>
<script type="text/javascript" src="./js/react-dom.development.js"></script>
<script type="text/javascript" src="./js/babel.min.js"></script>
<script type="text/babel">
// 类组件
// 根组件
class App extends React.Component {
state = {
count: 0
}
addCount = () => {
this.setState(preState => preState.count++, () => console.log(this.state));
}
render() {
return (
<div>
<div>
{this.state.count}
</div>
<button onClick={this.addCount}>add</button>
</div>
);
}
}
// 渲染虚拟dom
ReactDOM.render(<App />, document.getElementById('root'));
</script>
</body>
受控组件与非受控组件
- 非受控组件:没有通过 state 控制的表单元素,它自己控制自身的值
- 受控组件:表单元素的值被 React 中 state 控制,这个表单元素就是受控组件。
下面的例子中 Head 组件中 input 的值由 state.name 决定,所以它是受控组件,而 Body 组件中 input 的值由它自己决定,所以它是非受控组件。
<body>
<div id="root"></div>
<script type="text/javascript" src="./js/react.development.js"></script>
<script type="text/javascript" src="./js/react-dom.development.js"></script>
<script type="text/javascript" src="./js/babel.min.js"></script>
<script type="text/babel">
// 类组件
class Head extends React.Component {
state = {
name: '孤城浪人',
age: 18
}
changeName = () => {
this.setState({ name: '孤城' });
}
render() {
return (
<div>
姓名:{this.state.name}<br />
年龄:{this.state.age}<br />
<input type="text" value={this.state.name} />
<button onClick={this.changeName}>changeName</button>
</div>
)
}
}
class Body extends React.Component {
render() {
return (
<div>
<input type="text" />
</div>
)
}
}
// 根组件
class App extends React.Component {
render() {
return (
<div>
<Head />
</div>
);
}
}
// 渲染虚拟dom
ReactDOM.render(<App />, document.getElementById('root'));
</script>
</body>
函数组件
函数组件就跟他的名字一样是一个函数,如下例子,必须要返回 UI 结构。
<body>
<div id="root"></div>
<script type="text/javascript" src="./js/react.development.js"></script>
<script type="text/javascript" src="./js/react-dom.development.js"></script>
<script type="text/javascript" src="./js/babel.min.js"></script>
<script type="text/babel">
// 渲染虚拟dom
function App() {
return (
<div>
我是 APP 组件
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'));
</script>
</body>
状态
在 16.8 版本以前函数组件是没有状态的,16.8 版本推出Hooks API就使得函数组件和类组件的功能性一致了,既有状态,也有了生命周期方法。这里只简单说一下 useState 和 useEffect 两个 API。
- useState:使用方法如下,让函数组件也有状态,注意: useState 不可以用 if…else
- useEffect:使用方法如下,当第二个参数为
[]
时会在组件挂载时就会重新调用第一个参数函数,若第二个参数为[name]
,那么每次 name 变化时就会重新调用第一个参数函数。
function App() {
const [ name, setName ] = useState();
useEffect(() => {
setName('孤城浪人');
}, [])
return (
<div>
姓名:{name}
</div>
)
}
更改状态就调用就调用 useState 方法返回的第二个参数,他是一个函数,将新值作为参数调用就行了。
受控组件
因为函数组件有了状态,那么这里受控组件就和类组件的受控与非受控组件非常相似了。
我是孤城浪人,一名正在前端路上摸爬滚打的菜鸟,欢迎你的关注。