UNPKG

9.92 kBMarkdownView Raw
1Composi
2=======
3
4Contents
5--------
6- [Components](./components.md)
7- [JSX](./jsx.md)
8- [Hyperx](./hyperx.md)
9- [Hyperscript](./hyperscript.md)
10- [Mount and Render](./render.md)
11- [State](./state.md)
12- [Lifecycle Methods](./lifecycle.md)
13- [Events](./events.md)
14- [Styles](./styles.md)
15- [Unmount](./unmount.md)
16- [Installation](../README.md)
17- [Third Party Libraries](./third-party.md)
18- Functional Components
19- [Deployment](./deployment.md)
20
21Functional Components
22---------------------
23The component architecture is based on classes. If you favor functional programing over OOP, you might prefer to use functional components. Functional Components are always stateless, so you will want to use them with some kind of state management, such as Redux, Mobx, etc.
24
25Virtual Dom
26-----------
27
28When you create components with the Component class, you have a reference for the component's properties, methods, etc. This also provides a point of reference for the virtual dom. In contrast, functional components do not have this reference for their virtual dom. Instead, with functional components, the scope in which the function is invoked becomes the scope for the virtual dom. This means as your project grows and code gets out over many files, the scope of a functional component can be spread across several files. For components this is not a problem. For functional components this means the first time it gets invoked in a new scope, the previous virtual dom will be replaced by a new one. Unless you are creating a list with 10,000 or more items, you won't notice any inefficencies. However, it does result in more layout thrashing than when creating class-based components.
29
30Creating Functional Components
31------------------------------
32
33Functional components use [JSX](./jsx.md) to create markup, but technically you could also use [Hyperx](./hyperx.md) with ES6 template literals. Here we're going to look at using JSX.
34
35To create a functional component, you start with a function, surprise! The function could accept some data, or not. It depends on what the function needs to return. If it is returning static content, no need for a parameter of data. If you want the function component to consume some data, then pass it in as a parameter. And of course you'll need to return some markup. In the example below we have a simple, function component that creates a header:
36
37```javascript
38// title.js:
39// We need to import the "h" function:
40import {h} from 'composi'
41
42// Define function that takes props for data:
43export function Title(props) {
44 // Return the header with name from props:
45 return (
46 <nav>
47 <h1>Hello, {props.name}!</h1>
48 </nav>
49 )
50}
51```
52
53If we were to do this with the Composi `h` function, it would look like this:
54
55```javascript
56// app.js:
57import {h} from 'composi'
58
59// Define function that takes props for data:
60export function Title(name) {
61 return h(
62 'nav', {}, h(
63 'h1', {}, name
64 )
65 )
66}
67```
68
69Both examples above create virtual nodes, so we will need a way to get them into the DOM. Composi provides the `render` function for that purpose. It works similar to React's `ReactDOM.render` function. It takes two arguments: a tag/vnode and a selector/element in which to insert the markup.
70
71In our `app.js` file we import `h` and `render` and then the `Title` functional component and render it to the DOM:
72
73```javascript
74// app.js:
75import {h, render} from 'composi'
76import {Title} from './components/title'
77
78// Define data for component:
79const name = 'World'
80
81// Render the component into header tag:
82render(<Title {...{name}}/>, 'header')
83```
84
85This will convert the functional component into a virtual node (vnode) and then convert that into actual nodes inside the header tag. Because a virtual node was created, we can re-render this later with new data and Composi will patch the DOM efficiently for us. In this case that would involve patching the text node of the `h1` tag.
86
87List with Map
88-------------
89
90Now lets create a functional component that takes an array and outputs a list of items. Notice that we are using [BEM](https://css-tricks.com/bem-101/) notation for class names to make styling easier.
91
92```javascript
93// list.js:
94import {h} from 'composi'
95
96export function List(props) {
97 return (
98 <div class='container--list'>
99 <ul class='list--fruits'>
100 {props.items.map(item => <li class='list--fruits__item'>{props.item}</li>)}
101 </ul>
102 </div>
103 )
104}
105```
106
107This list is consuming an `items` array:
108
109```javascript
110// items.js:
111export const items = ['Apples', 'Oranges', 'Bananas']
112```
113
114In our `app.js` file we put this all together:
115
116```javascript
117// app.js:
118import {h, render} from 'composi'
119import {Title} from './components/title'
120import {List} from './components/list'
121import {items} from './items'
122
123// Define data for Title component:
124const name = 'World'
125
126// Render component into header:
127render(<Title {...{name}}/>, 'header')
128
129// Render list component into section with items data:
130render(<List {...{items}}/>, 'section')
131```
132
133Custom Tags
134-----------
135
136We can break this list down a bit using a custom tag for the list item. We will need to pass the data down to the child component through its props:
137
138
139```javascript
140// list.js:
141import {h} from 'composi'
142
143function ListItem(props) {
144 return (
145 <li class='list--fruits__item'>{props.item}</li>
146 )
147}
148
149export function List(props) {
150 return (
151 <div class='container--list'>
152 <ul class='list--fruits'>
153 {props.items.map(item => <ListItem {...{item}}/>)}
154 </ul>
155 </div>
156 )
157}
158```
159
160Data Flows Down
161---------------
162
163When using custom tags inside a functional component, the data flows down, from the parent to the children. There is no two-way data binding. This makes it easy to reason about. If you need a child component to communicate some kind of data change to its parent, you can use events or any pubsub solution from NPM. We have a tiny and efficient pubsub module on NPM called [pubber](https://www.npmjs.com/package/pubber) that is perfect for these situations.
164
165Events
166------
167
168What if we wanted to make this list dynamic by allowing the user to add items? For that we need events. We'll add a text input and button so the user can enter an item. Since this doesn't involve any data consuming any data, their function just needs to return the markup for the nodes to be created. We'll call this function component `ListForm`:
169
170```javascript
171// list.js:
172import {h} from 'composi'
173
174function ListForm() {
175 return (
176 <p>
177 <input class='list--fruits__input--add' placeholder='Enter Item...' type="text"/>
178 <button class='list--fruits__button--add'>Add Item</button>
179 </p>
180 )
181}
182function ListItem(props) {
183 return (
184 <li>{props.item}</li>
185 )
186}
187
188export function List(props) {
189 return (
190 <div class='container--list'>
191 <ListForm />
192 <ul>
193 {props.items.map(item => <ListItem {...{item}}/>)}
194 </ul>
195 </div>
196 )
197}
198```
199
200handleEvent Interface
201---------------------
202
203We are going to use the `handleEvent` interface to wire up events for the component. We'll do this in a separate file and import it into our `app.js` file. To use `handleEvent` with a functional component we'll need to create an object with the `handleEvent` function on it. Then we'll use a standar event listener and pass the that object instead of a callback.
204
205Since we want to be able to add a new fruit to the list, we need to have access to the data and also the function component that renders the list. Therefore we import them into the `events.js` file.
206
207```javascript
208// events.js:
209import {h, render} from 'composi'
210import {items} from './items'
211import {List} from './components/list'
212
213export const events = {
214 addItem(e) {
215 const input = document.querySelector('.list__input--add')
216 const value = input.value
217 if (value) {
218 items.push(value)
219 render(<List {...{items}}/>, 'section')
220 input.value = ''
221 }
222 },
223 handleEvent(e) {
224 e.target.className === 'list__button--add' && this.addItem(e)
225 }
226}
227```
228
229As you can see above, we need to get the value of the input. If the value is truthy, we push it to the items array. Then we re-render the functional component list. Because this is a different scope than `app.js` where we initially rendered the list, the first time we add an item, the list will be created a new, replacing what was there. That's because this is a different scope than `app.js`. However, with every new addition, the render function will use the new virtual dom in this scope to patch the DOM efficiently.
230
231Using the handleEvent Object
232----------------------------
233
234Now that we have our `handleEvent` object defined, we need to wire it up to our component. We'll do that in our `app.js` file:
235
236```javascript
237// app.js:
238import {h, render} from 'composi'
239import {Title} from './components/title'
240import {List} from './components/list'
241import {events} from './events'
242import {items} from './items'
243
244// Define data for component:
245const name = 'World'
246// Render component into header:
247render(<Title {...{name}}/>, 'header')
248
249// Render list component into section:
250render(<List {...{items}}/>, 'section')
251
252// Add event listener to the component's container and pass in the handleEvent object instead of a callback:
253document.querySelector('.container--list').addEventListener('click', events)
254```
255
256And that's it. With that we now have an interactive functional component that updates in real time with a virtual dom.
257
258Component Class Advantages
259--------------------------
260
261Although you can build a complex app using nothing but functional components, there are certain conveniences when using the Component class. First and foremost is the fact that classes allow you to encapsulate functionality using properties and methods defined directly on the component. The second is the component itself enables more efficient use of the virtual dom because it keeps track of that internally.
262