Join accept messages should presumably be counted as downlinks, of course the fair usage policy doesn’t actually have automated software enforcement at this point.
Another reason you would want to avoid lots of rejoins is that you will use up the join nonce space - a given value of the join nonce can only be used once, so you have to have a way to make sure that each join uses a value never before used, which tends to require non-volatile memory on the node.
In terms of trying to use downlinks to control something, remember that class A nodes (which are all that are really supported) can only receive a downlink in response to an uplink. You either need to stage a downlink message and have it sit until there is an uplink it can reply to, or have code which waits for an uplink and then immediately sends the downlink request, fast enough that it can be encoded and sent to the gateway to be transmitted exactly one second after the end of the uplink packet.